flet 0.70.0.dev5776__py3-none-any.whl → 0.70.0.dev6145__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.

Potentially problematic release.


This version of flet might be problematic. Click here for more details.

Files changed (46) hide show
  1. flet/__init__.py +32 -4
  2. flet/components/__init__.py +0 -0
  3. flet/components/component.py +346 -0
  4. flet/components/component_decorator.py +24 -0
  5. flet/components/component_owned.py +22 -0
  6. flet/components/hooks/__init__.py +0 -0
  7. flet/components/hooks/hook.py +12 -0
  8. flet/components/hooks/use_callback.py +28 -0
  9. flet/components/hooks/use_context.py +91 -0
  10. flet/components/hooks/use_effect.py +104 -0
  11. flet/components/hooks/use_memo.py +52 -0
  12. flet/components/hooks/use_state.py +58 -0
  13. flet/components/memo.py +34 -0
  14. flet/components/observable.py +269 -0
  15. flet/components/public_utils.py +10 -0
  16. flet/components/utils.py +85 -0
  17. flet/controls/base_control.py +34 -10
  18. flet/controls/base_page.py +44 -40
  19. flet/controls/context.py +22 -1
  20. flet/controls/control_event.py +19 -2
  21. flet/controls/core/column.py +5 -0
  22. flet/controls/core/drag_target.py +17 -8
  23. flet/controls/core/row.py +5 -0
  24. flet/controls/core/view.py +6 -6
  25. flet/controls/cupertino/cupertino_icons.py +1 -1
  26. flet/controls/id_counter.py +24 -0
  27. flet/controls/material/divider.py +6 -0
  28. flet/controls/material/icons.py +1 -1
  29. flet/controls/material/textfield.py +10 -1
  30. flet/controls/material/vertical_divider.py +6 -0
  31. flet/controls/object_patch.py +434 -197
  32. flet/controls/page.py +203 -84
  33. flet/controls/services/haptic_feedback.py +0 -3
  34. flet/controls/services/shake_detector.py +0 -3
  35. flet/messaging/flet_socket_server.py +13 -6
  36. flet/messaging/session.py +103 -10
  37. flet/{controls/session_storage.py → messaging/session_store.py} +2 -2
  38. flet/version.py +1 -1
  39. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/METADATA +4 -5
  40. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/RECORD +43 -30
  41. flet/controls/cache.py +0 -87
  42. flet/controls/control_id.py +0 -22
  43. flet/controls/core/state_view.py +0 -60
  44. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/WHEEL +0 -0
  45. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/entry_points.txt +0 -0
  46. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union
7
7
 
8
8
  from flet.controls.context import _context_page, context
9
9
  from flet.controls.control_event import ControlEvent, get_event_field_type
10
- from flet.controls.control_id import ControlId
10
+ from flet.controls.id_counter import ControlId
11
11
  from flet.controls.keys import KeyValue
12
12
  from flet.controls.ref import Ref
13
13
  from flet.utils.from_dict import from_dict
@@ -119,7 +119,6 @@ class BaseControl:
119
119
  """
120
120
  Arbitrary data of any type.
121
121
  """
122
-
123
122
  key: Optional[KeyValue] = None
124
123
 
125
124
  ref: InitVar[Optional[Ref["BaseControl"]]] = None
@@ -173,18 +172,21 @@ class BaseControl:
173
172
  return parent_ref() if parent_ref else None
174
173
 
175
174
  @property
176
- def page(self) -> Optional[Union["Page", "BasePage"]]:
175
+ def page(self) -> Union["Page", "BasePage"]:
177
176
  """
178
177
  The page to which this control belongs to.
179
178
  """
180
- from .page import BasePage, Page
179
+ from .page import Page
181
180
 
182
181
  parent = self
183
182
  while parent:
184
- if isinstance(parent, (Page, BasePage)):
183
+ if isinstance(parent, (Page)):
185
184
  return parent
186
185
  parent = parent.parent
187
- return None
186
+ raise RuntimeError(
187
+ f"{self.__class__.__qualname__}({self._i}) "
188
+ "Control must be added to the page first"
189
+ )
188
190
 
189
191
  def is_isolated(self):
190
192
  return hasattr(self, "_isolated") and self._isolated
@@ -209,15 +211,25 @@ class BaseControl:
209
211
  """
210
212
  pass
211
213
 
214
+ def _before_update_safe(self):
215
+ frozen = getattr(self, "_frozen", None)
216
+ if frozen is not None:
217
+ del self._frozen
218
+
219
+ self.before_update()
220
+
221
+ if frozen is not None:
222
+ self._frozen = frozen
223
+
212
224
  def before_event(self, e: ControlEvent):
213
225
  return True
214
226
 
215
227
  def did_mount(self):
216
- controls_log.debug(f"{self._c}({self._i}).did_mount")
228
+ controls_log.debug(f"{self}.did_mount()")
217
229
  pass
218
230
 
219
231
  def will_unmount(self):
220
- controls_log.debug(f"{self._c}({self._i}).will_unmount")
232
+ controls_log.debug(f"{self}.will_unmount()")
221
233
  pass
222
234
 
223
235
  # public methods
@@ -239,7 +251,7 @@ class BaseControl:
239
251
  f"{self.__class__.__qualname__} Control must be added to the page first"
240
252
  )
241
253
 
242
- return await self.page.get_session().invoke_method(
254
+ return await self.page.session.invoke_method(
243
255
  self._i, method_name, arguments, timeout
244
256
  )
245
257
 
@@ -271,11 +283,13 @@ class BaseControl:
271
283
  _context_page.set(self.page)
272
284
  context.reset_auto_update()
273
285
 
286
+ controls_log.debug(f"Trigger event {self}.{field_name} {e}")
287
+
274
288
  assert self.page, (
275
289
  "Control must be added to a page before triggering events. "
276
290
  "Use page.add(control) or add it to a parent control that's on a page."
277
291
  )
278
- session = self.page.get_session()
292
+ session = self.page.session
279
293
 
280
294
  # Handle async and sync event handlers accordingly
281
295
  event_handler = getattr(self, field_name)
@@ -310,3 +324,13 @@ class BaseControl:
310
324
  event_handler(e)
311
325
 
312
326
  await session.after_event(session.index.get(self._i))
327
+
328
+ def _migrate_state(self, other: "BaseControl"):
329
+ if not isinstance(other, BaseControl):
330
+ return
331
+ self._i = other._i
332
+ if self.data is None:
333
+ self.data = other.data
334
+
335
+ def __str__(self):
336
+ return f"{self._c}({self._i} - {id(self)})"
@@ -159,33 +159,32 @@ class BasePage(AdaptiveControl):
159
159
  reported by the framework.
160
160
  """
161
161
 
162
- width: Optional[Number] = None
162
+ title: Optional[str] = None
163
+ """
164
+ Page or window title.
163
165
  """
164
- Page width in logical pixels.
165
166
 
166
- Note:
167
- - This property is read-only.
168
- - To get or set the full window height including window chrome (e.g.,
169
- title bar and borders) when running a Flet app on desktop,
170
- use the [`width`][flet.Window.] property of
171
- [`Page.window`][flet.] instead.
167
+ enable_screenshots: bool = False
168
+ """
169
+ Enable taking screenshots of the entire page with `take_screenshot` method.
172
170
  """
173
171
 
174
- height: Optional[Number] = None
172
+ on_resize: Optional[EventHandler["PageResizeEvent"]] = None
175
173
  """
176
- Page height in logical pixels.
174
+ Called when a user resizes a browser or native OS window containing Flet app, for
175
+ example:
177
176
 
178
- Note:
179
- - This property is read-only.
180
- - To get or set the full window height including window chrome (e.g.,
181
- title bar and borders) when running a Flet app on desktop,
182
- use the [`height`][flet.Window.] property of
183
- [`Page.window`][flet.] instead.
177
+ ```python
178
+ def page_resize(e):
179
+ print("New page size:", page.window.width, page.window_height)
180
+
181
+ page.on_resize = page_resize
182
+ ```
184
183
  """
185
184
 
186
- title: Optional[str] = None
185
+ on_media_change: Optional[EventHandler[PageMediaData]] = None
187
186
  """
188
- Page or window title.
187
+ Called when `media` has changed.
189
188
  """
190
189
 
191
190
  media: PageMediaData = field(
@@ -197,30 +196,35 @@ class BasePage(AdaptiveControl):
197
196
  )
198
197
  )
199
198
  """
200
- Represents the environmental metrics of a page or window.
201
- """
199
+ The current environmental metrics of the page or window.
202
200
 
203
- enable_screenshots: bool = False
204
- """
205
- Enable taking screenshots of the entire page with `take_screenshot` method.
201
+ This data is updated whenever the platform window or layout changes,
202
+ such as when rotating a device, resizing a browser window, or adjusting
203
+ system UI elements like the keyboard or safe areas.
206
204
  """
207
205
 
208
- on_resize: Optional[EventHandler["PageResizeEvent"]] = None
206
+ width: Optional[Number] = None
209
207
  """
210
- Called when a user resizes a browser or native OS window containing Flet app, for
211
- example:
212
-
213
- ```python
214
- def page_resize(e):
215
- print("New page size:", page.window.width, page.window_height)
208
+ Page width in logical pixels.
216
209
 
217
- page.on_resize = page_resize
218
- ```
210
+ Note:
211
+ - This property is read-only.
212
+ - To get or set the full window height including window chrome (e.g.,
213
+ title bar and borders) when running a Flet app on desktop,
214
+ use the [`width`][flet.Window.width] property of
215
+ [`Page.window`][flet.Page.window] instead.
219
216
  """
220
217
 
221
- on_media_change: Optional[EventHandler[PageMediaData]] = None
218
+ height: Optional[Number] = None
222
219
  """
223
- Called when `media` has changed.
220
+ Page height in logical pixels.
221
+
222
+ Note:
223
+ - This property is read-only.
224
+ - To get or set the full window height including window chrome (e.g.,
225
+ title bar and borders) when running a Flet app on desktop,
226
+ use the [`height`][flet.Window.height] property of
227
+ [`Page.window`][flet.Page.window] instead.
224
228
  """
225
229
 
226
230
  _overlay: "Overlay" = field(default_factory=lambda: Overlay())
@@ -492,29 +496,29 @@ class BasePage(AdaptiveControl):
492
496
 
493
497
  # horizontal_alignment
494
498
  @property
495
- def horizontal_alignment(self) -> Optional[CrossAxisAlignment]:
499
+ def horizontal_alignment(self) -> CrossAxisAlignment:
496
500
  return self.__default_view().horizontal_alignment
497
501
 
498
502
  @horizontal_alignment.setter
499
- def horizontal_alignment(self, value: Optional[CrossAxisAlignment]):
503
+ def horizontal_alignment(self, value: CrossAxisAlignment):
500
504
  self.__default_view().horizontal_alignment = value
501
505
 
502
506
  # vertical_alignment
503
507
  @property
504
- def vertical_alignment(self) -> Optional[MainAxisAlignment]:
508
+ def vertical_alignment(self) -> MainAxisAlignment:
505
509
  return self.__default_view().vertical_alignment
506
510
 
507
511
  @vertical_alignment.setter
508
- def vertical_alignment(self, value: Optional[MainAxisAlignment]):
512
+ def vertical_alignment(self, value: MainAxisAlignment):
509
513
  self.__default_view().vertical_alignment = value
510
514
 
511
515
  # spacing
512
516
  @property
513
- def spacing(self) -> Optional[Number]:
517
+ def spacing(self) -> Number:
514
518
  return self.__default_view().spacing
515
519
 
516
520
  @spacing.setter
517
- def spacing(self, value: Optional[Number]):
521
+ def spacing(self, value: Number):
518
522
  self.__default_view().spacing = value
519
523
 
520
524
  # padding
flet/controls/context.py CHANGED
@@ -13,6 +13,9 @@ class Context:
13
13
  Context instance is accessed via [`flet.context`][flet.context].
14
14
  """
15
15
 
16
+ def __init__(self) -> None:
17
+ self.__components_mode = False
18
+
16
19
  @property
17
20
  def page(self) -> "Page":
18
21
  """
@@ -95,6 +98,21 @@ class Context:
95
98
  """
96
99
  _update_behavior_context_var.get()._auto_update_enabled = False
97
100
 
101
+ def enable_components_mode(self):
102
+ """
103
+ Enables components mode in the current context.
104
+ """
105
+ self.__components_mode = True
106
+
107
+ def is_components_mode(self) -> bool:
108
+ """
109
+ Returns whether the current context is in components mode.
110
+
111
+ Returns:
112
+ `True` if in components mode, `False` otherwise.
113
+ """
114
+ return self.__components_mode
115
+
98
116
  def auto_update_enabled(self) -> bool:
99
117
  """
100
118
  Returns whether auto-update is enabled in the current context.
@@ -102,7 +120,10 @@ class Context:
102
120
  Returns:
103
121
  `True` if auto-update is enabled, `False` otherwise.
104
122
  """
105
- return _update_behavior_context_var.get()._auto_update_enabled
123
+ return (
124
+ not self.__components_mode
125
+ and _update_behavior_context_var.get()._auto_update_enabled
126
+ )
106
127
 
107
128
  def reset_auto_update(self):
108
129
  """
@@ -32,14 +32,17 @@ __all__ = [
32
32
 
33
33
  def get_event_field_type(control: Any, field_name: str):
34
34
  frame = inspect.currentframe().f_back
35
- globalns = sys.modules[control.__class__.__module__].__dict__
36
35
  localns = frame.f_globals.copy()
37
36
  localns.update(frame.f_locals)
38
37
 
39
38
  merged_annotations = {}
39
+ annotation_modules = {}
40
40
 
41
41
  for cls in control.__class__.__mro__:
42
42
  annotations = getattr(cls, "__annotations__", {})
43
+ module = sys.modules.get(cls.__module__)
44
+ module_dict = module.__dict__ if module else {}
45
+
43
46
  for name, annotation in annotations.items():
44
47
  if get_origin(annotation) is InitVar or str(annotation).startswith(
45
48
  "dataclasses.InitVar"
@@ -47,12 +50,25 @@ def get_event_field_type(control: Any, field_name: str):
47
50
  continue # Skip InitVar
48
51
  if name not in merged_annotations:
49
52
  merged_annotations[name] = annotation
53
+ annotation_modules[name] = module_dict
50
54
 
51
55
  if field_name not in merged_annotations:
52
56
  return None
53
57
 
54
58
  annotation = merged_annotations[field_name]
55
59
 
60
+ globalns = {}
61
+ current_module = sys.modules.get(control.__class__.__module__)
62
+ if current_module:
63
+ globalns.update(current_module.__dict__)
64
+
65
+ owner_module_dict = annotation_modules.get(field_name)
66
+ if owner_module_dict:
67
+ for key, value in owner_module_dict.items():
68
+ globalns.setdefault(key, value)
69
+
70
+ globalns.setdefault("__builtins__", __builtins__)
71
+
56
72
  try:
57
73
  # Resolve forward refs manually
58
74
  if isinstance(annotation, ForwardRef):
@@ -80,7 +96,8 @@ class Event(Generic[EventControlType]):
80
96
  control: EventControlType = field(repr=False)
81
97
 
82
98
  @property
83
- def page(self) -> Optional[Union["Page", "BasePage"]]:
99
+ def page(self) -> Union["Page", "BasePage"]:
100
+ assert self.control.page
84
101
  return self.control.page
85
102
 
86
103
  @property
@@ -65,6 +65,11 @@ class Column(LayoutControl, ScrollableControl, AdaptiveControl):
65
65
  is `True`.
66
66
  """
67
67
 
68
+ intrinsic_width: bool = False
69
+ """
70
+ If `True`, the Column will be as wide as the widest child control.
71
+ """
72
+
68
73
  def init(self):
69
74
  super().init()
70
75
  self._internals["host_expanded"] = True
@@ -1,9 +1,10 @@
1
- from dataclasses import dataclass
2
- from typing import Optional
1
+ from dataclasses import dataclass, field
2
+ from typing import Optional, cast
3
3
 
4
4
  from flet.controls.base_control import control
5
5
  from flet.controls.control import Control
6
6
  from flet.controls.control_event import Event, EventHandler
7
+ from flet.controls.core.draggable import Draggable
7
8
  from flet.controls.transform import Offset
8
9
 
9
10
  __all__ = [
@@ -15,14 +16,22 @@ __all__ = [
15
16
 
16
17
 
17
18
  @dataclass
18
- class DragWillAcceptEvent(Event["DragTarget"]):
19
+ class DragEventBase(Event["DragTarget"]):
20
+ src_id: Optional[int]
21
+ src: Draggable = field(init=False)
22
+
23
+ def __post_init__(self):
24
+ if self.src_id is not None:
25
+ self.src = cast(Draggable, self.page.get_control(self.src_id))
26
+
27
+
28
+ @dataclass
29
+ class DragWillAcceptEvent(DragEventBase):
19
30
  accept: bool
20
- src_id: int
21
31
 
22
32
 
23
33
  @dataclass
24
- class DragTargetEvent(Event["DragTarget"]):
25
- src_id: int
34
+ class DragTargetEvent(DragEventBase):
26
35
  x: float
27
36
  y: float
28
37
 
@@ -32,8 +41,8 @@ class DragTargetEvent(Event["DragTarget"]):
32
41
 
33
42
 
34
43
  @dataclass
35
- class DragTargetLeaveEvent(Event["DragTarget"]):
36
- src_id: Optional[int]
44
+ class DragTargetLeaveEvent(DragEventBase):
45
+ pass
37
46
 
38
47
 
39
48
  @control("DragTarget")
flet/controls/core/row.py CHANGED
@@ -68,6 +68,11 @@ class Row(LayoutControl, ScrollableControl, AdaptiveControl):
68
68
  How the runs should be placed in the cross-axis when `wrap=True`.
69
69
  """
70
70
 
71
+ intrinsic_height: bool = False
72
+ """
73
+ If `True`, the Row will be as tall as the tallest child control.
74
+ """
75
+
71
76
  def init(self):
72
77
  super().init()
73
78
  self._internals["host_expanded"] = True
@@ -37,12 +37,6 @@ class View(ScrollableControl, LayoutControl):
37
37
  control, so it has a similar behavior and shares same properties.
38
38
  """
39
39
 
40
- route: Optional[str] = None
41
- """
42
- View's route - not currently used by Flet framework, but can be used in a user
43
- program to update [`Page.route`][flet.] when a view popped.
44
- """
45
-
46
40
  controls: list[BaseControl] = field(default_factory=list)
47
41
  """
48
42
  A list of controls to display.
@@ -71,6 +65,12 @@ class View(ScrollableControl, LayoutControl):
71
65
  ///
72
66
  """
73
67
 
68
+ route: Optional[str] = field(default_factory=lambda: "/")
69
+ """
70
+ View's route - not currently used by Flet framework, but can be used in a user
71
+ program to update [`Page.route`][flet.Page.route] when a view popped.
72
+ """
73
+
74
74
  appbar: Optional[Union[AppBar, CupertinoAppBar]] = None
75
75
  """
76
76
  An [`AppBar`][flet.] control to display at the top of
@@ -4,7 +4,7 @@ Flet Cupertino Icons
4
4
  To generate/update this file run from the root of the repository:
5
5
 
6
6
  ```
7
- uv run ci/generate_icons.py
7
+ uv run .github/scripts/generate_icons.py
8
8
  ```
9
9
  """
10
10
 
@@ -0,0 +1,24 @@
1
+ import itertools
2
+ import threading
3
+ from typing import Optional
4
+
5
+ from flet.utils.locks import NopeLock
6
+ from flet.utils.platform_utils import is_pyodide
7
+
8
+
9
+ class IdCounter:
10
+ def __init__(
11
+ self, start: int = 1, step: int = 1, lock: Optional[threading.Lock] = None
12
+ ):
13
+ self._counter = itertools.count(start, step)
14
+ self._lock = lock or (NopeLock() if is_pyodide() else threading.Lock())
15
+
16
+ def next(self) -> int:
17
+ with self._lock:
18
+ return next(self._counter)
19
+
20
+ def __call__(self) -> int: # for dataclass default_factory
21
+ return self.next()
22
+
23
+
24
+ ControlId = IdCounter(start=3)
@@ -1,6 +1,7 @@
1
1
  from typing import Optional
2
2
 
3
3
  from flet.controls.base_control import control
4
+ from flet.controls.border_radius import BorderRadiusValue
4
5
  from flet.controls.control import Control
5
6
  from flet.controls.types import ColorValue, Number
6
7
 
@@ -62,6 +63,11 @@ class Divider(Control):
62
63
  If that is also `None`, defaults to `0.0`.
63
64
  """
64
65
 
66
+ radius: Optional[BorderRadiusValue] = None
67
+ """
68
+ The border radius of the divider.
69
+ """
70
+
65
71
  def before_update(self):
66
72
  super().before_update()
67
73
  if self.height is not None and self.height < 0:
@@ -4,7 +4,7 @@ Flet Material Icons
4
4
  To generate/update this file run from the root of the repository:
5
5
 
6
6
  ```
7
- uv run ci/generate_icons.py
7
+ uv run .github/scripts/generate_icons.py
8
8
  ```
9
9
  """
10
10
 
@@ -3,7 +3,7 @@ from enum import Enum
3
3
  from typing import Optional, Union
4
4
 
5
5
  from flet.controls.adaptive_control import AdaptiveControl
6
- from flet.controls.base_control import control
6
+ from flet.controls.base_control import BaseControl, control
7
7
  from flet.controls.control_event import ControlEventHandler
8
8
  from flet.controls.core.autofill_group import AutofillHint
9
9
  from flet.controls.material.form_field_control import FormFieldControl
@@ -424,6 +424,15 @@ class TextField(FormFieldControl, AdaptiveControl):
424
424
  TBD
425
425
  """
426
426
 
427
+ def _migrate_state(self, other: BaseControl):
428
+ super()._migrate_state(other)
429
+ if (
430
+ isinstance(other, TextField)
431
+ and self.value is None
432
+ and self.value != other.value
433
+ ):
434
+ self.value = other.value
435
+
427
436
  def before_update(self):
428
437
  super().before_update()
429
438
  if self.min_lines is not None and self.min_lines <= 0:
@@ -1,6 +1,7 @@
1
1
  from typing import Optional
2
2
 
3
3
  from flet.controls.base_control import control
4
+ from flet.controls.border_radius import BorderRadiusValue
4
5
  from flet.controls.control import Control
5
6
  from flet.controls.types import ColorValue, Number
6
7
 
@@ -67,6 +68,11 @@ class VerticalDivider(Control):
67
68
  If that's is also `None`, defaults to `0.0`.
68
69
  """
69
70
 
71
+ radius: Optional[BorderRadiusValue] = None
72
+ """
73
+ The border radius of the divider.
74
+ """
75
+
70
76
  def before_update(self):
71
77
  super().before_update()
72
78
  if self.width is not None and self.width < 0: