pythonnative 0.6.0__py3-none-any.whl → 0.7.0__py3-none-any.whl

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.
@@ -4,52 +4,23 @@ Each function returns an :class:`Element` describing a native UI widget.
4
4
  These are pure data — no native views are created until the reconciler
5
5
  mounts the element tree.
6
6
 
7
- Layout properties (``width``, ``height``, ``flex``, ``margin``,
8
- ``min_width``, ``max_width``, ``min_height``, ``max_height``,
9
- ``align_self``) are supported by all components.
10
- """
11
-
12
- from typing import Any, Callable, Dict, List, Optional, Union
13
-
14
- from .element import Element
7
+ All visual and layout properties are passed via the ``style`` parameter,
8
+ which accepts a dict or a list of dicts (later entries override earlier).
15
9
 
16
- # ======================================================================
17
- # Shared helpers
18
- # ======================================================================
10
+ Layout properties supported by all components::
19
11
 
20
- PaddingValue = Union[int, float, Dict[str, Union[int, float]]]
21
- MarginValue = Union[int, float, Dict[str, Union[int, float]]]
12
+ width, height, flex, margin, min_width, max_width, min_height,
13
+ max_height, align_self
22
14
 
15
+ Container-specific layout properties (Column / Row)::
23
16
 
24
- def _filter_none(**kwargs: Any) -> Dict[str, Any]:
25
- """Return *kwargs* with ``None``-valued entries removed."""
26
- return {k: v for k, v in kwargs.items() if v is not None}
27
-
17
+ spacing, padding, align_items, justify_content
18
+ """
28
19
 
29
- def _layout_props(
30
- width: Optional[float] = None,
31
- height: Optional[float] = None,
32
- flex: Optional[float] = None,
33
- margin: Optional[MarginValue] = None,
34
- min_width: Optional[float] = None,
35
- max_width: Optional[float] = None,
36
- min_height: Optional[float] = None,
37
- max_height: Optional[float] = None,
38
- align_self: Optional[str] = None,
39
- ) -> Dict[str, Any]:
40
- """Collect common layout props into a dict (excluding Nones)."""
41
- return _filter_none(
42
- width=width,
43
- height=height,
44
- flex=flex,
45
- margin=margin,
46
- min_width=min_width,
47
- max_width=max_width,
48
- min_height=min_height,
49
- max_height=max_height,
50
- align_self=align_self,
51
- )
20
+ from typing import Any, Callable, Dict, List, Optional
52
21
 
22
+ from .element import Element
23
+ from .style import StyleValue, resolve_style
53
24
 
54
25
  # ======================================================================
55
26
  # Leaf components
@@ -59,46 +30,16 @@ def _layout_props(
59
30
  def Text(
60
31
  text: str = "",
61
32
  *,
62
- font_size: Optional[float] = None,
63
- color: Optional[str] = None,
64
- bold: bool = False,
65
- text_align: Optional[str] = None,
66
- background_color: Optional[str] = None,
67
- max_lines: Optional[int] = None,
68
- width: Optional[float] = None,
69
- height: Optional[float] = None,
70
- flex: Optional[float] = None,
71
- margin: Optional[MarginValue] = None,
72
- min_width: Optional[float] = None,
73
- max_width: Optional[float] = None,
74
- min_height: Optional[float] = None,
75
- max_height: Optional[float] = None,
76
- align_self: Optional[str] = None,
33
+ style: StyleValue = None,
77
34
  key: Optional[str] = None,
78
35
  ) -> Element:
79
- """Display text."""
80
- props = _filter_none(
81
- text=text,
82
- font_size=font_size,
83
- color=color,
84
- bold=bold or None,
85
- text_align=text_align,
86
- background_color=background_color,
87
- max_lines=max_lines,
88
- )
89
- props.update(
90
- _layout_props(
91
- width=width,
92
- height=height,
93
- flex=flex,
94
- margin=margin,
95
- min_width=min_width,
96
- max_width=max_width,
97
- min_height=min_height,
98
- max_height=max_height,
99
- align_self=align_self,
100
- )
101
- )
36
+ """Display text.
37
+
38
+ Style properties: ``font_size``, ``color``, ``bold``, ``text_align``,
39
+ ``background_color``, ``max_lines``, plus common layout props.
40
+ """
41
+ props: Dict[str, Any] = {"text": text}
42
+ props.update(resolve_style(style))
102
43
  return Element("Text", props, [], key=key)
103
44
 
104
45
 
@@ -106,46 +47,21 @@ def Button(
106
47
  title: str = "",
107
48
  *,
108
49
  on_click: Optional[Callable[[], None]] = None,
109
- color: Optional[str] = None,
110
- background_color: Optional[str] = None,
111
- font_size: Optional[float] = None,
112
50
  enabled: bool = True,
113
- width: Optional[float] = None,
114
- height: Optional[float] = None,
115
- flex: Optional[float] = None,
116
- margin: Optional[MarginValue] = None,
117
- min_width: Optional[float] = None,
118
- max_width: Optional[float] = None,
119
- min_height: Optional[float] = None,
120
- max_height: Optional[float] = None,
121
- align_self: Optional[str] = None,
51
+ style: StyleValue = None,
122
52
  key: Optional[str] = None,
123
53
  ) -> Element:
124
- """Create a tappable button."""
54
+ """Create a tappable button.
55
+
56
+ Style properties: ``color``, ``background_color``, ``font_size``,
57
+ plus common layout props.
58
+ """
125
59
  props: Dict[str, Any] = {"title": title}
126
60
  if on_click is not None:
127
61
  props["on_click"] = on_click
128
- if color is not None:
129
- props["color"] = color
130
- if background_color is not None:
131
- props["background_color"] = background_color
132
- if font_size is not None:
133
- props["font_size"] = font_size
134
62
  if not enabled:
135
63
  props["enabled"] = False
136
- props.update(
137
- _layout_props(
138
- width=width,
139
- height=height,
140
- flex=flex,
141
- margin=margin,
142
- min_width=min_width,
143
- max_width=max_width,
144
- min_height=min_height,
145
- max_height=max_height,
146
- align_self=align_self,
147
- )
148
- )
64
+ props.update(resolve_style(style))
149
65
  return Element("Button", props, [], key=key)
150
66
 
151
67
 
@@ -155,21 +71,14 @@ def TextInput(
155
71
  placeholder: str = "",
156
72
  on_change: Optional[Callable[[str], None]] = None,
157
73
  secure: bool = False,
158
- font_size: Optional[float] = None,
159
- color: Optional[str] = None,
160
- background_color: Optional[str] = None,
161
- width: Optional[float] = None,
162
- height: Optional[float] = None,
163
- flex: Optional[float] = None,
164
- margin: Optional[MarginValue] = None,
165
- min_width: Optional[float] = None,
166
- max_width: Optional[float] = None,
167
- min_height: Optional[float] = None,
168
- max_height: Optional[float] = None,
169
- align_self: Optional[str] = None,
74
+ style: StyleValue = None,
170
75
  key: Optional[str] = None,
171
76
  ) -> Element:
172
- """Create a single-line text entry field."""
77
+ """Create a single-line text entry field.
78
+
79
+ Style properties: ``font_size``, ``color``, ``background_color``,
80
+ plus common layout props.
81
+ """
173
82
  props: Dict[str, Any] = {"value": value}
174
83
  if placeholder:
175
84
  props["placeholder"] = placeholder
@@ -177,63 +86,27 @@ def TextInput(
177
86
  props["on_change"] = on_change
178
87
  if secure:
179
88
  props["secure"] = True
180
- if font_size is not None:
181
- props["font_size"] = font_size
182
- if color is not None:
183
- props["color"] = color
184
- if background_color is not None:
185
- props["background_color"] = background_color
186
- props.update(
187
- _layout_props(
188
- width=width,
189
- height=height,
190
- flex=flex,
191
- margin=margin,
192
- min_width=min_width,
193
- max_width=max_width,
194
- min_height=min_height,
195
- max_height=max_height,
196
- align_self=align_self,
197
- )
198
- )
89
+ props.update(resolve_style(style))
199
90
  return Element("TextInput", props, [], key=key)
200
91
 
201
92
 
202
93
  def Image(
203
94
  source: str = "",
204
95
  *,
205
- width: Optional[float] = None,
206
- height: Optional[float] = None,
207
96
  scale_type: Optional[str] = None,
208
- background_color: Optional[str] = None,
209
- flex: Optional[float] = None,
210
- margin: Optional[MarginValue] = None,
211
- min_width: Optional[float] = None,
212
- max_width: Optional[float] = None,
213
- min_height: Optional[float] = None,
214
- max_height: Optional[float] = None,
215
- align_self: Optional[str] = None,
97
+ style: StyleValue = None,
216
98
  key: Optional[str] = None,
217
99
  ) -> Element:
218
- """Display an image from a resource path or URL."""
219
- props = _filter_none(
220
- source=source or None,
221
- width=width,
222
- height=height,
223
- scale_type=scale_type,
224
- background_color=background_color,
225
- )
226
- props.update(
227
- _layout_props(
228
- flex=flex,
229
- margin=margin,
230
- min_width=min_width,
231
- max_width=max_width,
232
- min_height=min_height,
233
- max_height=max_height,
234
- align_self=align_self,
235
- )
236
- )
100
+ """Display an image from a resource path or URL.
101
+
102
+ Style properties: ``background_color``, plus common layout props.
103
+ """
104
+ props: Dict[str, Any] = {}
105
+ if source:
106
+ props["source"] = source
107
+ if scale_type is not None:
108
+ props["scale_type"] = scale_type
109
+ props.update(resolve_style(style))
237
110
  return Element("Image", props, [], key=key)
238
111
 
239
112
 
@@ -241,68 +114,52 @@ def Switch(
241
114
  *,
242
115
  value: bool = False,
243
116
  on_change: Optional[Callable[[bool], None]] = None,
244
- width: Optional[float] = None,
245
- height: Optional[float] = None,
246
- flex: Optional[float] = None,
247
- margin: Optional[MarginValue] = None,
248
- align_self: Optional[str] = None,
117
+ style: StyleValue = None,
249
118
  key: Optional[str] = None,
250
119
  ) -> Element:
251
120
  """Create a toggle switch."""
252
121
  props: Dict[str, Any] = {"value": value}
253
122
  if on_change is not None:
254
123
  props["on_change"] = on_change
255
- props.update(_layout_props(width=width, height=height, flex=flex, margin=margin, align_self=align_self))
124
+ props.update(resolve_style(style))
256
125
  return Element("Switch", props, [], key=key)
257
126
 
258
127
 
259
128
  def ProgressBar(
260
129
  *,
261
130
  value: float = 0.0,
262
- background_color: Optional[str] = None,
263
- width: Optional[float] = None,
264
- height: Optional[float] = None,
265
- flex: Optional[float] = None,
266
- margin: Optional[MarginValue] = None,
267
- align_self: Optional[str] = None,
131
+ style: StyleValue = None,
268
132
  key: Optional[str] = None,
269
133
  ) -> Element:
270
134
  """Show determinate progress (0.0 – 1.0)."""
271
- props = _filter_none(value=value, background_color=background_color)
272
- props.update(_layout_props(width=width, height=height, flex=flex, margin=margin, align_self=align_self))
135
+ props: Dict[str, Any] = {"value": value}
136
+ props.update(resolve_style(style))
273
137
  return Element("ProgressBar", props, [], key=key)
274
138
 
275
139
 
276
140
  def ActivityIndicator(
277
141
  *,
278
142
  animating: bool = True,
279
- width: Optional[float] = None,
280
- height: Optional[float] = None,
281
- margin: Optional[MarginValue] = None,
282
- align_self: Optional[str] = None,
143
+ style: StyleValue = None,
283
144
  key: Optional[str] = None,
284
145
  ) -> Element:
285
146
  """Show an indeterminate loading spinner."""
286
147
  props: Dict[str, Any] = {"animating": animating}
287
- props.update(_layout_props(width=width, height=height, margin=margin, align_self=align_self))
148
+ props.update(resolve_style(style))
288
149
  return Element("ActivityIndicator", props, [], key=key)
289
150
 
290
151
 
291
152
  def WebView(
292
153
  *,
293
154
  url: str = "",
294
- width: Optional[float] = None,
295
- height: Optional[float] = None,
296
- flex: Optional[float] = None,
297
- margin: Optional[MarginValue] = None,
298
- align_self: Optional[str] = None,
155
+ style: StyleValue = None,
299
156
  key: Optional[str] = None,
300
157
  ) -> Element:
301
158
  """Embed web content."""
302
159
  props: Dict[str, Any] = {}
303
160
  if url:
304
161
  props["url"] = url
305
- props.update(_layout_props(width=width, height=height, flex=flex, margin=margin, align_self=align_self))
162
+ props.update(resolve_style(style))
306
163
  return Element("WebView", props, [], key=key)
307
164
 
308
165
 
@@ -312,11 +169,36 @@ def Spacer(
312
169
  flex: Optional[float] = None,
313
170
  key: Optional[str] = None,
314
171
  ) -> Element:
315
- """Insert empty space with an optional fixed size."""
316
- props = _filter_none(size=size, flex=flex)
172
+ """Insert empty space with an optional fixed size or flex weight."""
173
+ props: Dict[str, Any] = {}
174
+ if size is not None:
175
+ props["size"] = size
176
+ if flex is not None:
177
+ props["flex"] = flex
317
178
  return Element("Spacer", props, [], key=key)
318
179
 
319
180
 
181
+ def Slider(
182
+ *,
183
+ value: float = 0.0,
184
+ min_value: float = 0.0,
185
+ max_value: float = 1.0,
186
+ on_change: Optional[Callable[[float], None]] = None,
187
+ style: StyleValue = None,
188
+ key: Optional[str] = None,
189
+ ) -> Element:
190
+ """Continuous value slider."""
191
+ props: Dict[str, Any] = {
192
+ "value": value,
193
+ "min_value": min_value,
194
+ "max_value": max_value,
195
+ }
196
+ if on_change is not None:
197
+ props["on_change"] = on_change
198
+ props.update(resolve_style(style))
199
+ return Element("Slider", props, [], key=key)
200
+
201
+
320
202
  # ======================================================================
321
203
  # Container components
322
204
  # ======================================================================
@@ -324,146 +206,82 @@ def Spacer(
324
206
 
325
207
  def Column(
326
208
  *children: Element,
327
- spacing: float = 0,
328
- padding: Optional[PaddingValue] = None,
329
- alignment: Optional[str] = None,
330
- background_color: Optional[str] = None,
331
- width: Optional[float] = None,
332
- height: Optional[float] = None,
333
- flex: Optional[float] = None,
334
- margin: Optional[MarginValue] = None,
335
- min_width: Optional[float] = None,
336
- max_width: Optional[float] = None,
337
- min_height: Optional[float] = None,
338
- max_height: Optional[float] = None,
339
- align_self: Optional[str] = None,
209
+ style: StyleValue = None,
340
210
  key: Optional[str] = None,
341
211
  ) -> Element:
342
- """Arrange children vertically."""
343
- props = _filter_none(
344
- spacing=spacing or None,
345
- padding=padding,
346
- alignment=alignment,
347
- background_color=background_color,
348
- )
349
- props.update(
350
- _layout_props(
351
- width=width,
352
- height=height,
353
- flex=flex,
354
- margin=margin,
355
- min_width=min_width,
356
- max_width=max_width,
357
- min_height=min_height,
358
- max_height=max_height,
359
- align_self=align_self,
360
- )
361
- )
212
+ """Arrange children vertically.
213
+
214
+ Style properties: ``spacing``, ``padding``, ``align_items``,
215
+ ``justify_content``, ``background_color``, plus common layout props.
216
+
217
+ ``align_items`` controls cross-axis (horizontal) alignment:
218
+ ``"stretch"`` (default), ``"flex_start"``/``"leading"``,
219
+ ``"center"``, ``"flex_end"``/``"trailing"``.
220
+
221
+ ``justify_content`` controls main-axis (vertical) distribution:
222
+ ``"flex_start"`` (default), ``"center"``, ``"flex_end"``,
223
+ ``"space_between"``, ``"space_around"``, ``"space_evenly"``.
224
+ """
225
+ props: Dict[str, Any] = {}
226
+ props.update(resolve_style(style))
362
227
  return Element("Column", props, list(children), key=key)
363
228
 
364
229
 
365
230
  def Row(
366
231
  *children: Element,
367
- spacing: float = 0,
368
- padding: Optional[PaddingValue] = None,
369
- alignment: Optional[str] = None,
370
- background_color: Optional[str] = None,
371
- width: Optional[float] = None,
372
- height: Optional[float] = None,
373
- flex: Optional[float] = None,
374
- margin: Optional[MarginValue] = None,
375
- min_width: Optional[float] = None,
376
- max_width: Optional[float] = None,
377
- min_height: Optional[float] = None,
378
- max_height: Optional[float] = None,
379
- align_self: Optional[str] = None,
232
+ style: StyleValue = None,
380
233
  key: Optional[str] = None,
381
234
  ) -> Element:
382
- """Arrange children horizontally."""
383
- props = _filter_none(
384
- spacing=spacing or None,
385
- padding=padding,
386
- alignment=alignment,
387
- background_color=background_color,
388
- )
389
- props.update(
390
- _layout_props(
391
- width=width,
392
- height=height,
393
- flex=flex,
394
- margin=margin,
395
- min_width=min_width,
396
- max_width=max_width,
397
- min_height=min_height,
398
- max_height=max_height,
399
- align_self=align_self,
400
- )
401
- )
235
+ """Arrange children horizontally.
236
+
237
+ Style properties: ``spacing``, ``padding``, ``align_items``,
238
+ ``justify_content``, ``background_color``, plus common layout props.
239
+
240
+ ``align_items`` controls cross-axis (vertical) alignment:
241
+ ``"stretch"`` (default), ``"flex_start"``/``"top"``,
242
+ ``"center"``, ``"flex_end"``/``"bottom"``.
243
+
244
+ ``justify_content`` controls main-axis (horizontal) distribution:
245
+ ``"flex_start"`` (default), ``"center"``, ``"flex_end"``,
246
+ ``"space_between"``, ``"space_around"``, ``"space_evenly"``.
247
+ """
248
+ props: Dict[str, Any] = {}
249
+ props.update(resolve_style(style))
402
250
  return Element("Row", props, list(children), key=key)
403
251
 
404
252
 
405
253
  def ScrollView(
406
254
  child: Optional[Element] = None,
407
255
  *,
408
- background_color: Optional[str] = None,
409
- width: Optional[float] = None,
410
- height: Optional[float] = None,
411
- flex: Optional[float] = None,
412
- margin: Optional[MarginValue] = None,
413
- align_self: Optional[str] = None,
256
+ style: StyleValue = None,
414
257
  key: Optional[str] = None,
415
258
  ) -> Element:
416
259
  """Wrap a single child in a scrollable container."""
417
260
  children = [child] if child is not None else []
418
- props = _filter_none(background_color=background_color)
419
- props.update(_layout_props(width=width, height=height, flex=flex, margin=margin, align_self=align_self))
261
+ props: Dict[str, Any] = {}
262
+ props.update(resolve_style(style))
420
263
  return Element("ScrollView", props, children, key=key)
421
264
 
422
265
 
423
266
  def View(
424
267
  *children: Element,
425
- background_color: Optional[str] = None,
426
- padding: Optional[PaddingValue] = None,
427
- width: Optional[float] = None,
428
- height: Optional[float] = None,
429
- flex: Optional[float] = None,
430
- margin: Optional[MarginValue] = None,
431
- min_width: Optional[float] = None,
432
- max_width: Optional[float] = None,
433
- min_height: Optional[float] = None,
434
- max_height: Optional[float] = None,
435
- align_self: Optional[str] = None,
268
+ style: StyleValue = None,
436
269
  key: Optional[str] = None,
437
270
  ) -> Element:
438
271
  """Generic container view (``UIView`` / ``android.view.View``)."""
439
- props = _filter_none(
440
- background_color=background_color,
441
- padding=padding,
442
- )
443
- props.update(
444
- _layout_props(
445
- width=width,
446
- height=height,
447
- flex=flex,
448
- margin=margin,
449
- min_width=min_width,
450
- max_width=max_width,
451
- min_height=min_height,
452
- max_height=max_height,
453
- align_self=align_self,
454
- )
455
- )
272
+ props: Dict[str, Any] = {}
273
+ props.update(resolve_style(style))
456
274
  return Element("View", props, list(children), key=key)
457
275
 
458
276
 
459
277
  def SafeAreaView(
460
278
  *children: Element,
461
- background_color: Optional[str] = None,
462
- padding: Optional[PaddingValue] = None,
279
+ style: StyleValue = None,
463
280
  key: Optional[str] = None,
464
281
  ) -> Element:
465
282
  """Container that respects safe area insets (notch, status bar)."""
466
- props = _filter_none(background_color=background_color, padding=padding)
283
+ props: Dict[str, Any] = {}
284
+ props.update(resolve_style(style))
467
285
  return Element("SafeAreaView", props, list(children), key=key)
468
286
 
469
287
 
@@ -472,7 +290,7 @@ def Modal(
472
290
  visible: bool = False,
473
291
  on_dismiss: Optional[Callable[[], None]] = None,
474
292
  title: Optional[str] = None,
475
- background_color: Optional[str] = None,
293
+ style: StyleValue = None,
476
294
  key: Optional[str] = None,
477
295
  ) -> Element:
478
296
  """Overlay modal dialog.
@@ -484,34 +302,10 @@ def Modal(
484
302
  props["on_dismiss"] = on_dismiss
485
303
  if title is not None:
486
304
  props["title"] = title
487
- if background_color is not None:
488
- props["background_color"] = background_color
305
+ props.update(resolve_style(style))
489
306
  return Element("Modal", props, list(children), key=key)
490
307
 
491
308
 
492
- def Slider(
493
- *,
494
- value: float = 0.0,
495
- min_value: float = 0.0,
496
- max_value: float = 1.0,
497
- on_change: Optional[Callable[[float], None]] = None,
498
- width: Optional[float] = None,
499
- margin: Optional[MarginValue] = None,
500
- align_self: Optional[str] = None,
501
- key: Optional[str] = None,
502
- ) -> Element:
503
- """Continuous value slider."""
504
- props: Dict[str, Any] = {
505
- "value": value,
506
- "min_value": min_value,
507
- "max_value": max_value,
508
- }
509
- if on_change is not None:
510
- props["on_change"] = on_change
511
- props.update(_layout_props(width=width, margin=margin, align_self=align_self))
512
- return Element("Slider", props, [], key=key)
513
-
514
-
515
309
  def Pressable(
516
310
  child: Optional[Element] = None,
517
311
  *,
@@ -535,20 +329,14 @@ def FlatList(
535
329
  render_item: Optional[Callable[[Any, int], Element]] = None,
536
330
  key_extractor: Optional[Callable[[Any, int], str]] = None,
537
331
  separator_height: float = 0,
538
- background_color: Optional[str] = None,
539
- width: Optional[float] = None,
540
- height: Optional[float] = None,
541
- flex: Optional[float] = None,
542
- margin: Optional[MarginValue] = None,
543
- align_self: Optional[str] = None,
332
+ style: StyleValue = None,
544
333
  key: Optional[str] = None,
545
334
  ) -> Element:
546
335
  """Scrollable list that renders items from *data* using *render_item*.
547
336
 
548
337
  Each item is rendered by calling ``render_item(item, index)``. If
549
338
  ``key_extractor`` is provided, it is called as ``key_extractor(item, index)``
550
- to produce a stable key for each child element. This enables the
551
- reconciler to preserve widget state across data changes.
339
+ to produce a stable key for each child element.
552
340
  """
553
341
  items: List[Element] = []
554
342
  for i, item in enumerate(data or []):
@@ -557,7 +345,7 @@ def FlatList(
557
345
  el = Element(el.type, el.props, el.children, key=key_extractor(item, i))
558
346
  items.append(el)
559
347
 
560
- inner = Column(*items, spacing=separator_height)
561
- sv_props = _filter_none(background_color=background_color)
562
- sv_props.update(_layout_props(width=width, height=height, flex=flex, margin=margin, align_self=align_self))
348
+ inner = Column(*items, style={"spacing": separator_height} if separator_height else None)
349
+ sv_props: Dict[str, Any] = {}
350
+ sv_props.update(resolve_style(style))
563
351
  return Element("ScrollView", sv_props, [inner], key=key)