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.
pythonnative/hooks.py CHANGED
@@ -1,7 +1,8 @@
1
1
  """Hook primitives for function components.
2
2
 
3
3
  Provides React-like hooks for managing state, effects, memoisation,
4
- and context within function components decorated with :func:`component`.
4
+ context, and navigation within function components decorated with
5
+ :func:`component`.
5
6
 
6
7
  Usage::
7
8
 
@@ -18,7 +19,7 @@ Usage::
18
19
 
19
20
  import inspect
20
21
  import threading
21
- from typing import Any, Callable, List, Optional, Tuple, TypeVar
22
+ from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar
22
23
 
23
24
  from .element import Element
24
25
 
@@ -246,6 +247,52 @@ def Provider(context: Context, value: Any, child: Element) -> Element:
246
247
  return Element("__Provider__", {"__context__": context, "__value__": value}, [child])
247
248
 
248
249
 
250
+ # ======================================================================
251
+ # Navigation
252
+ # ======================================================================
253
+
254
+ _NavigationContext: Context = create_context(None)
255
+
256
+
257
+ class NavigationHandle:
258
+ """Object returned by :func:`use_navigation` providing push/pop/get_args.
259
+
260
+ Navigates by component reference rather than string path, e.g.::
261
+
262
+ nav = pn.use_navigation()
263
+ nav.push(DetailScreen, args={"id": 42})
264
+ """
265
+
266
+ def __init__(self, host: Any) -> None:
267
+ self._host = host
268
+
269
+ def push(self, page: Any, args: Optional[Dict[str, Any]] = None) -> None:
270
+ """Navigate forward to *page* (a ``@component`` function or class)."""
271
+ self._host._push(page, args)
272
+
273
+ def pop(self) -> None:
274
+ """Navigate back to the previous screen."""
275
+ self._host._pop()
276
+
277
+ def get_args(self) -> Dict[str, Any]:
278
+ """Return arguments passed from the previous screen."""
279
+ return self._host._get_nav_args()
280
+
281
+
282
+ def use_navigation() -> NavigationHandle:
283
+ """Return a :class:`NavigationHandle` for the current screen.
284
+
285
+ Must be called inside a ``@component`` function rendered by PythonNative.
286
+ """
287
+ handle = use_context(_NavigationContext)
288
+ if handle is None:
289
+ raise RuntimeError(
290
+ "use_navigation() called outside a PythonNative page. "
291
+ "Ensure your component is rendered via create_page()."
292
+ )
293
+ return handle
294
+
295
+
249
296
  # ======================================================================
250
297
  # @component decorator
251
298
  # ======================================================================
@@ -277,6 +277,7 @@ def _register_android_handlers(registry: NativeViewRegistry) -> None: # noqa: C
277
277
  _apply_layout(native_view, changed)
278
278
 
279
279
  def _apply(self, ll: Any, props: Dict[str, Any]) -> None:
280
+ Gravity = jclass("android.view.Gravity")
280
281
  if "spacing" in props and props["spacing"]:
281
282
  px = _dp(float(props["spacing"]))
282
283
  GradientDrawable = jclass("android.graphics.drawable.GradientDrawable")
@@ -288,17 +289,31 @@ def _register_android_handlers(registry: NativeViewRegistry) -> None: # noqa: C
288
289
  if "padding" in props:
289
290
  left, top, right, bottom = _resolve_padding(props["padding"])
290
291
  ll.setPadding(_dp(left), _dp(top), _dp(right), _dp(bottom))
291
- if "alignment" in props and props["alignment"]:
292
- Gravity = jclass("android.view.Gravity")
293
- mapping = {
292
+ gravity = 0
293
+ ai = props.get("align_items") or props.get("alignment")
294
+ if ai:
295
+ cross_map = {
296
+ "stretch": Gravity.FILL_HORIZONTAL,
294
297
  "fill": Gravity.FILL_HORIZONTAL,
295
- "center": Gravity.CENTER_HORIZONTAL,
298
+ "flex_start": Gravity.START,
296
299
  "leading": Gravity.START,
297
300
  "start": Gravity.START,
301
+ "center": Gravity.CENTER_HORIZONTAL,
302
+ "flex_end": Gravity.END,
298
303
  "trailing": Gravity.END,
299
304
  "end": Gravity.END,
300
305
  }
301
- ll.setGravity(mapping.get(props["alignment"], Gravity.FILL_HORIZONTAL))
306
+ gravity |= cross_map.get(ai, 0)
307
+ jc = props.get("justify_content")
308
+ if jc:
309
+ main_map = {
310
+ "flex_start": Gravity.TOP,
311
+ "center": Gravity.CENTER_VERTICAL,
312
+ "flex_end": Gravity.BOTTOM,
313
+ }
314
+ gravity |= main_map.get(jc, 0)
315
+ if gravity:
316
+ ll.setGravity(gravity)
302
317
  if "background_color" in props and props["background_color"] is not None:
303
318
  ll.setBackgroundColor(parse_color_int(props["background_color"]))
304
319
 
@@ -326,6 +341,7 @@ def _register_android_handlers(registry: NativeViewRegistry) -> None: # noqa: C
326
341
  _apply_layout(native_view, changed)
327
342
 
328
343
  def _apply(self, ll: Any, props: Dict[str, Any]) -> None:
344
+ Gravity = jclass("android.view.Gravity")
329
345
  if "spacing" in props and props["spacing"]:
330
346
  px = _dp(float(props["spacing"]))
331
347
  GradientDrawable = jclass("android.graphics.drawable.GradientDrawable")
@@ -337,15 +353,29 @@ def _register_android_handlers(registry: NativeViewRegistry) -> None: # noqa: C
337
353
  if "padding" in props:
338
354
  left, top, right, bottom = _resolve_padding(props["padding"])
339
355
  ll.setPadding(_dp(left), _dp(top), _dp(right), _dp(bottom))
340
- if "alignment" in props and props["alignment"]:
341
- Gravity = jclass("android.view.Gravity")
342
- mapping = {
356
+ gravity = 0
357
+ ai = props.get("align_items") or props.get("alignment")
358
+ if ai:
359
+ cross_map = {
360
+ "stretch": Gravity.FILL_VERTICAL,
343
361
  "fill": Gravity.FILL_VERTICAL,
344
- "center": Gravity.CENTER_VERTICAL,
362
+ "flex_start": Gravity.TOP,
345
363
  "top": Gravity.TOP,
364
+ "center": Gravity.CENTER_VERTICAL,
365
+ "flex_end": Gravity.BOTTOM,
346
366
  "bottom": Gravity.BOTTOM,
347
367
  }
348
- ll.setGravity(mapping.get(props["alignment"], Gravity.FILL_VERTICAL))
368
+ gravity |= cross_map.get(ai, 0)
369
+ jc = props.get("justify_content")
370
+ if jc:
371
+ main_map = {
372
+ "flex_start": Gravity.START,
373
+ "center": Gravity.CENTER_HORIZONTAL,
374
+ "flex_end": Gravity.END,
375
+ }
376
+ gravity |= main_map.get(jc, 0)
377
+ if gravity:
378
+ ll.setGravity(gravity)
349
379
  if "background_color" in props and props["background_color"] is not None:
350
380
  ll.setBackgroundColor(parse_color_int(props["background_color"]))
351
381
 
@@ -910,9 +940,29 @@ def _register_ios_handlers(registry: NativeViewRegistry) -> None: # noqa: C901
910
940
  def _apply(self, sv: Any, props: Dict[str, Any]) -> None:
911
941
  if "spacing" in props and props["spacing"]:
912
942
  sv.setSpacing_(float(props["spacing"]))
913
- if "alignment" in props and props["alignment"]:
914
- mapping = {"fill": 0, "leading": 1, "top": 1, "center": 3, "trailing": 4, "bottom": 4}
915
- sv.setAlignment_(mapping.get(props["alignment"], 0))
943
+ ai = props.get("align_items") or props.get("alignment")
944
+ if ai:
945
+ alignment_map = {
946
+ "stretch": 0,
947
+ "fill": 0,
948
+ "flex_start": 1,
949
+ "leading": 1,
950
+ "center": 3,
951
+ "flex_end": 4,
952
+ "trailing": 4,
953
+ }
954
+ sv.setAlignment_(alignment_map.get(ai, 0))
955
+ jc = props.get("justify_content")
956
+ if jc:
957
+ distribution_map = {
958
+ "flex_start": 0,
959
+ "center": 0,
960
+ "flex_end": 0,
961
+ "space_between": 3,
962
+ "space_around": 4,
963
+ "space_evenly": 4,
964
+ }
965
+ sv.setDistribution_(distribution_map.get(jc, 0))
916
966
  if "background_color" in props and props["background_color"] is not None:
917
967
  sv.setBackgroundColor_(_uicolor(props["background_color"]))
918
968
  if "padding" in props:
@@ -950,9 +1000,29 @@ def _register_ios_handlers(registry: NativeViewRegistry) -> None: # noqa: C901
950
1000
  def _apply(self, sv: Any, props: Dict[str, Any]) -> None:
951
1001
  if "spacing" in props and props["spacing"]:
952
1002
  sv.setSpacing_(float(props["spacing"]))
953
- if "alignment" in props and props["alignment"]:
954
- mapping = {"fill": 0, "leading": 1, "top": 1, "center": 3, "trailing": 4, "bottom": 4}
955
- sv.setAlignment_(mapping.get(props["alignment"], 0))
1003
+ ai = props.get("align_items") or props.get("alignment")
1004
+ if ai:
1005
+ alignment_map = {
1006
+ "stretch": 0,
1007
+ "fill": 0,
1008
+ "flex_start": 1,
1009
+ "top": 1,
1010
+ "center": 3,
1011
+ "flex_end": 4,
1012
+ "bottom": 4,
1013
+ }
1014
+ sv.setAlignment_(alignment_map.get(ai, 0))
1015
+ jc = props.get("justify_content")
1016
+ if jc:
1017
+ distribution_map = {
1018
+ "flex_start": 0,
1019
+ "center": 0,
1020
+ "flex_end": 0,
1021
+ "space_between": 3,
1022
+ "space_around": 4,
1023
+ "space_evenly": 4,
1024
+ }
1025
+ sv.setDistribution_(distribution_map.get(jc, 0))
956
1026
  if "background_color" in props and props["background_color"] is not None:
957
1027
  sv.setBackgroundColor_(_uicolor(props["background_color"]))
958
1028