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.
- flet/__init__.py +32 -4
- flet/components/__init__.py +0 -0
- flet/components/component.py +346 -0
- flet/components/component_decorator.py +24 -0
- flet/components/component_owned.py +22 -0
- flet/components/hooks/__init__.py +0 -0
- flet/components/hooks/hook.py +12 -0
- flet/components/hooks/use_callback.py +28 -0
- flet/components/hooks/use_context.py +91 -0
- flet/components/hooks/use_effect.py +104 -0
- flet/components/hooks/use_memo.py +52 -0
- flet/components/hooks/use_state.py +58 -0
- flet/components/memo.py +34 -0
- flet/components/observable.py +269 -0
- flet/components/public_utils.py +10 -0
- flet/components/utils.py +85 -0
- flet/controls/base_control.py +34 -10
- flet/controls/base_page.py +44 -40
- flet/controls/context.py +22 -1
- flet/controls/control_event.py +19 -2
- flet/controls/core/column.py +5 -0
- flet/controls/core/drag_target.py +17 -8
- flet/controls/core/row.py +5 -0
- flet/controls/core/view.py +6 -6
- flet/controls/cupertino/cupertino_icons.py +1 -1
- flet/controls/id_counter.py +24 -0
- flet/controls/material/divider.py +6 -0
- flet/controls/material/icons.py +1 -1
- flet/controls/material/textfield.py +10 -1
- flet/controls/material/vertical_divider.py +6 -0
- flet/controls/object_patch.py +434 -197
- flet/controls/page.py +203 -84
- flet/controls/services/haptic_feedback.py +0 -3
- flet/controls/services/shake_detector.py +0 -3
- flet/messaging/flet_socket_server.py +13 -6
- flet/messaging/session.py +103 -10
- flet/{controls/session_storage.py → messaging/session_store.py} +2 -2
- flet/version.py +1 -1
- {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/METADATA +4 -5
- {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/RECORD +43 -30
- flet/controls/cache.py +0 -87
- flet/controls/control_id.py +0 -22
- flet/controls/core/state_view.py +0 -60
- {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/WHEEL +0 -0
- {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/entry_points.txt +0 -0
- {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev6145.dist-info}/top_level.txt +0 -0
flet/__init__.py
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
from flet.app import app, app_async, run, run_async
|
|
2
|
+
from flet.components.component import Component
|
|
3
|
+
from flet.components.component_decorator import component
|
|
4
|
+
from flet.components.hooks.use_callback import use_callback
|
|
5
|
+
from flet.components.hooks.use_context import create_context, use_context
|
|
6
|
+
from flet.components.hooks.use_effect import (
|
|
7
|
+
on_mounted,
|
|
8
|
+
on_unmounted,
|
|
9
|
+
on_updated,
|
|
10
|
+
use_effect,
|
|
11
|
+
)
|
|
12
|
+
from flet.components.hooks.use_memo import use_memo
|
|
13
|
+
from flet.components.hooks.use_state import use_state
|
|
14
|
+
from flet.components.memo import memo
|
|
15
|
+
from flet.components.observable import Observable, observable
|
|
16
|
+
from flet.components.public_utils import unwrap_component
|
|
2
17
|
from flet.controls import alignment, border, border_radius, margin, padding
|
|
3
18
|
from flet.controls.adaptive_control import AdaptiveControl
|
|
4
19
|
from flet.controls.alignment import Alignment, Axis
|
|
@@ -48,7 +63,6 @@ from flet.controls.buttons import (
|
|
|
48
63
|
ShapeBorder,
|
|
49
64
|
StadiumBorder,
|
|
50
65
|
)
|
|
51
|
-
from flet.controls.cache import cache
|
|
52
66
|
from flet.controls.colors import Colors
|
|
53
67
|
from flet.controls.context import Context, context
|
|
54
68
|
from flet.controls.control import Control
|
|
@@ -116,7 +130,6 @@ from flet.controls.core.screenshot import Screenshot
|
|
|
116
130
|
from flet.controls.core.semantics import Semantics
|
|
117
131
|
from flet.controls.core.shader_mask import ShaderMask
|
|
118
132
|
from flet.controls.core.stack import Stack, StackFit
|
|
119
|
-
from flet.controls.core.state_view import StateView
|
|
120
133
|
from flet.controls.core.text import (
|
|
121
134
|
Text,
|
|
122
135
|
TextAffinity,
|
|
@@ -218,6 +231,7 @@ from flet.controls.gradients import (
|
|
|
218
231
|
SweepGradient,
|
|
219
232
|
)
|
|
220
233
|
from flet.controls.icon_data import IconData
|
|
234
|
+
from flet.controls.id_counter import IdCounter
|
|
221
235
|
from flet.controls.keys import Key, KeyValue, ScrollKey, ValueKey
|
|
222
236
|
from flet.controls.layout_control import ConstrainedControl, LayoutControl
|
|
223
237
|
from flet.controls.margin import Margin, MarginValue
|
|
@@ -574,6 +588,7 @@ __all__ = [
|
|
|
574
588
|
"ColorValue",
|
|
575
589
|
"Colors",
|
|
576
590
|
"Column",
|
|
591
|
+
"Component",
|
|
577
592
|
"ConstrainedControl",
|
|
578
593
|
"Container",
|
|
579
594
|
"Context",
|
|
@@ -691,6 +706,7 @@ __all__ = [
|
|
|
691
706
|
"IconDataOrControl",
|
|
692
707
|
"IconTheme",
|
|
693
708
|
"Icons",
|
|
709
|
+
"IdCounter",
|
|
694
710
|
"Image",
|
|
695
711
|
"ImageRepeat",
|
|
696
712
|
"InputBorder",
|
|
@@ -749,6 +765,7 @@ __all__ = [
|
|
|
749
765
|
"NotchShape",
|
|
750
766
|
"Number",
|
|
751
767
|
"NumbersOnlyInputFilter",
|
|
768
|
+
"Observable",
|
|
752
769
|
"Offset",
|
|
753
770
|
"OffsetValue",
|
|
754
771
|
"OnReorderEvent",
|
|
@@ -845,7 +862,6 @@ __all__ = [
|
|
|
845
862
|
"Stack",
|
|
846
863
|
"StackFit",
|
|
847
864
|
"StadiumBorder",
|
|
848
|
-
"StateView",
|
|
849
865
|
"StoragePaths",
|
|
850
866
|
"StrOrControl",
|
|
851
867
|
"StrokeCap",
|
|
@@ -920,16 +936,28 @@ __all__ = [
|
|
|
920
936
|
"app_async",
|
|
921
937
|
"border",
|
|
922
938
|
"border_radius",
|
|
923
|
-
"
|
|
939
|
+
"component",
|
|
924
940
|
"context",
|
|
925
941
|
"control",
|
|
942
|
+
"create_context",
|
|
926
943
|
"cupertino_colors",
|
|
927
944
|
"cupertino_icons",
|
|
928
945
|
"dropdown",
|
|
929
946
|
"dropdownm2",
|
|
930
947
|
"icons",
|
|
931
948
|
"margin",
|
|
949
|
+
"memo",
|
|
950
|
+
"observable",
|
|
951
|
+
"on_mounted",
|
|
952
|
+
"on_unmounted",
|
|
953
|
+
"on_updated",
|
|
932
954
|
"padding",
|
|
933
955
|
"run",
|
|
934
956
|
"run_async",
|
|
957
|
+
"unwrap_component",
|
|
958
|
+
"use_callback",
|
|
959
|
+
"use_context",
|
|
960
|
+
"use_effect",
|
|
961
|
+
"use_memo",
|
|
962
|
+
"use_state",
|
|
935
963
|
]
|
|
File without changes
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import weakref
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any, Callable, TypeVar
|
|
8
|
+
|
|
9
|
+
from flet.components.hooks.hook import Hook
|
|
10
|
+
from flet.components.hooks.use_effect import EffectHook
|
|
11
|
+
from flet.components.observable import Observable, ObservableSubscription
|
|
12
|
+
from flet.components.utils import (
|
|
13
|
+
_CURRENT_RENDERER,
|
|
14
|
+
shallow_compare_args_and_kwargs,
|
|
15
|
+
)
|
|
16
|
+
from flet.controls.base_control import BaseControl, control
|
|
17
|
+
from flet.controls.context import context
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("flet_components")
|
|
20
|
+
logger.setLevel(logging.INFO)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class _ComponentState:
|
|
25
|
+
hooks: list[Hook] = field(default_factory=list)
|
|
26
|
+
hook_cursor: int = 0
|
|
27
|
+
mounted: bool = False
|
|
28
|
+
is_dirty: bool = False
|
|
29
|
+
observable_subscriptions: list[ObservableSubscription] = field(default_factory=list)
|
|
30
|
+
last_args: tuple[Any, ...] = field(default_factory=tuple)
|
|
31
|
+
last_kwargs: dict[str, Any] = field(default_factory=dict)
|
|
32
|
+
last_b: Any = None
|
|
33
|
+
|
|
34
|
+
def change_owner(self, new_owner: Any):
|
|
35
|
+
for hook in self.hooks:
|
|
36
|
+
hook.component = new_owner
|
|
37
|
+
for sub in self.observable_subscriptions:
|
|
38
|
+
sub.component = new_owner
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
HookTypeT = TypeVar("HookTypeT", bound=Hook)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@control("C")
|
|
45
|
+
class Component(BaseControl):
|
|
46
|
+
fn: Callable[..., Any] = field(metadata={"skip": True})
|
|
47
|
+
args: tuple[Any, ...] = field(default_factory=tuple, metadata={"skip": True})
|
|
48
|
+
kwargs: dict[str, Any] = field(default_factory=dict, metadata={"skip": True})
|
|
49
|
+
_parent_component: weakref.ref[Component] | None = field(
|
|
50
|
+
default=None, metadata={"skip": True}
|
|
51
|
+
)
|
|
52
|
+
_state: _ComponentState = field(
|
|
53
|
+
default_factory=_ComponentState, metadata={"skip": True}
|
|
54
|
+
)
|
|
55
|
+
_contexts: dict[object, Any] = field(default_factory=dict, metadata={"skip": True})
|
|
56
|
+
memoized: bool = field(default=False, metadata={"skip": True})
|
|
57
|
+
_stale: bool = field(default=False, metadata={"skip": True})
|
|
58
|
+
|
|
59
|
+
_b: Any = None # body
|
|
60
|
+
|
|
61
|
+
def _migrate_state(self, other: BaseControl):
|
|
62
|
+
super()._migrate_state(other)
|
|
63
|
+
logger.debug("%s._migrate_state(%s)", self, other)
|
|
64
|
+
if not isinstance(other, Component):
|
|
65
|
+
return
|
|
66
|
+
self._state = other._state
|
|
67
|
+
self._state.change_owner(self)
|
|
68
|
+
other._stale = True
|
|
69
|
+
|
|
70
|
+
def update(self):
|
|
71
|
+
if self._stale:
|
|
72
|
+
logger.debug("%s.update(): skipping (stale)", self)
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
logger.debug(
|
|
76
|
+
"%s.update(), memoized: %s",
|
|
77
|
+
self,
|
|
78
|
+
self.memoized,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
self._state.is_dirty = False
|
|
82
|
+
|
|
83
|
+
# new rendering
|
|
84
|
+
self._state.hook_cursor = 0
|
|
85
|
+
self._detach_observable_subscriptions()
|
|
86
|
+
self._subscribe_observable_args(self.args, self.kwargs)
|
|
87
|
+
|
|
88
|
+
b = Renderer(self).render(self.fn, *self.args, **self.kwargs)
|
|
89
|
+
|
|
90
|
+
for item in b if isinstance(b, list) else [b] if b is not None else []:
|
|
91
|
+
object.__setattr__(item, "_frozen", True)
|
|
92
|
+
|
|
93
|
+
if self.memoized and b is not None:
|
|
94
|
+
logger.debug("%s.update(): memoizing", self)
|
|
95
|
+
self._state.last_b = b
|
|
96
|
+
self._state.last_args = self.args
|
|
97
|
+
self._state.last_kwargs = self.kwargs
|
|
98
|
+
|
|
99
|
+
# patch component
|
|
100
|
+
if b is not None:
|
|
101
|
+
context.page.session.patch_control(
|
|
102
|
+
prev_control={"_b": self._b},
|
|
103
|
+
control={"_b": b},
|
|
104
|
+
parent=self,
|
|
105
|
+
path=[],
|
|
106
|
+
frozen=True,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
self._b = b
|
|
110
|
+
self._run_render_effects()
|
|
111
|
+
|
|
112
|
+
def before_update(self):
|
|
113
|
+
logger.debug("%s.before_update(), memoized: %s", self, self.memoized)
|
|
114
|
+
is_dirty = self._state.is_dirty
|
|
115
|
+
self._state.is_dirty = False
|
|
116
|
+
|
|
117
|
+
if (
|
|
118
|
+
self.memoized
|
|
119
|
+
and not is_dirty
|
|
120
|
+
and shallow_compare_args_and_kwargs(
|
|
121
|
+
self._state.last_args, self._state.last_kwargs, self.args, self.kwargs
|
|
122
|
+
)
|
|
123
|
+
and self._state.last_b is not None
|
|
124
|
+
):
|
|
125
|
+
logger.debug("%s.before_update(): skipping (memo)", self)
|
|
126
|
+
self._b = self._state.last_b
|
|
127
|
+
|
|
128
|
+
# fix parent
|
|
129
|
+
for item in self._b if isinstance(self._b, list) else [self._b]:
|
|
130
|
+
object.__setattr__(item, "_parent", weakref.ref(self))
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
self._state.hook_cursor = 0
|
|
134
|
+
self._detach_observable_subscriptions()
|
|
135
|
+
self._subscribe_observable_args(self.args, self.kwargs)
|
|
136
|
+
b = Renderer(self).render(self.fn, *self.args, **self.kwargs)
|
|
137
|
+
|
|
138
|
+
for item in b if isinstance(b, list) else [b] if b is not None else []:
|
|
139
|
+
object.__setattr__(item, "_frozen", True)
|
|
140
|
+
|
|
141
|
+
if self.memoized and b is not None:
|
|
142
|
+
logger.debug("%s.before_update(): memoizing", self)
|
|
143
|
+
self._state.last_b = b
|
|
144
|
+
self._state.last_args = self.args
|
|
145
|
+
self._state.last_kwargs = self.kwargs
|
|
146
|
+
self._b = b
|
|
147
|
+
self._run_render_effects()
|
|
148
|
+
|
|
149
|
+
def _schedule_update(self):
|
|
150
|
+
logger.debug("%s.schedule_update()", self)
|
|
151
|
+
self._state.is_dirty = True
|
|
152
|
+
context.page.session.schedule_update(self)
|
|
153
|
+
|
|
154
|
+
def _schedule_effect(self, hook: EffectHook, is_cleanup: bool = False):
|
|
155
|
+
logger.debug("%s.schedule_effect(%s, %s)", self, hook, is_cleanup)
|
|
156
|
+
context.page.session.schedule_effect(hook, is_cleanup)
|
|
157
|
+
|
|
158
|
+
def _subscribe_observable_args(self, args: tuple[Any, ...], kwargs: dict[str, Any]):
|
|
159
|
+
for a in args:
|
|
160
|
+
if isinstance(a, Observable):
|
|
161
|
+
self._attach_observable_subscription(a)
|
|
162
|
+
for v in kwargs.values():
|
|
163
|
+
if isinstance(v, Observable):
|
|
164
|
+
self._attach_observable_subscription(v)
|
|
165
|
+
|
|
166
|
+
def _attach_observable_subscription(self, observable: Observable):
|
|
167
|
+
# Use weak refs to avoid cycles
|
|
168
|
+
logger.debug("%s._attach_observable_subscription(%s)", self, observable)
|
|
169
|
+
|
|
170
|
+
self._state.observable_subscriptions.append(
|
|
171
|
+
ObservableSubscription(owner=self, observable=observable)
|
|
172
|
+
)
|
|
173
|
+
return self._state.observable_subscriptions[-1]
|
|
174
|
+
|
|
175
|
+
def _detach_observable_subscription(self, subscription: ObservableSubscription):
|
|
176
|
+
if subscription in self._state.observable_subscriptions:
|
|
177
|
+
subscription.dispose()
|
|
178
|
+
self._state.observable_subscriptions.remove(subscription)
|
|
179
|
+
|
|
180
|
+
def _detach_observable_subscriptions(self):
|
|
181
|
+
for subscription in self._state.observable_subscriptions:
|
|
182
|
+
subscription.dispose()
|
|
183
|
+
self._state.observable_subscriptions.clear()
|
|
184
|
+
|
|
185
|
+
def use_hook(self, default: Callable[[], HookTypeT]) -> HookTypeT:
|
|
186
|
+
hook_cursor = self._state.hook_cursor
|
|
187
|
+
|
|
188
|
+
i = hook_cursor
|
|
189
|
+
hook_cursor += 1
|
|
190
|
+
|
|
191
|
+
if i >= len(self._state.hooks):
|
|
192
|
+
self._state.hooks.append(default())
|
|
193
|
+
|
|
194
|
+
self._state.hook_cursor = hook_cursor
|
|
195
|
+
return self._state.hooks[i] # type: ignore
|
|
196
|
+
|
|
197
|
+
def _run_mount_effects(self):
|
|
198
|
+
if self._state.hooks:
|
|
199
|
+
logger.debug("%s._run_mount_effects()", self)
|
|
200
|
+
for hook in self._state.hooks:
|
|
201
|
+
if isinstance(hook, EffectHook):
|
|
202
|
+
# all effects are running on mount
|
|
203
|
+
self._schedule_effect(hook, is_cleanup=False)
|
|
204
|
+
|
|
205
|
+
def _run_render_effects(self):
|
|
206
|
+
if not self._state.mounted:
|
|
207
|
+
return
|
|
208
|
+
if self._state.hooks:
|
|
209
|
+
logger.debug("%s._run_render_effects()", self)
|
|
210
|
+
for hook in self._state.hooks:
|
|
211
|
+
if isinstance(hook, EffectHook) and hook.deps != []:
|
|
212
|
+
if callable(hook.cleanup):
|
|
213
|
+
self._schedule_effect(hook, is_cleanup=False)
|
|
214
|
+
if (
|
|
215
|
+
hook.deps is None
|
|
216
|
+
or hook.prev_deps is None
|
|
217
|
+
or hook.deps != hook.prev_deps
|
|
218
|
+
):
|
|
219
|
+
self._schedule_effect(hook, is_cleanup=False)
|
|
220
|
+
|
|
221
|
+
def _run_unmount_effects(self):
|
|
222
|
+
if self._state.hooks:
|
|
223
|
+
logger.debug("%s._run_unmount_effects()", self)
|
|
224
|
+
for hook in self._state.hooks:
|
|
225
|
+
# all effects are running on unmount
|
|
226
|
+
if isinstance(hook, EffectHook) and callable(hook.cleanup):
|
|
227
|
+
self._schedule_effect(hook, is_cleanup=True)
|
|
228
|
+
|
|
229
|
+
def did_mount(self):
|
|
230
|
+
super().did_mount()
|
|
231
|
+
self._state.mounted = True
|
|
232
|
+
self._run_mount_effects()
|
|
233
|
+
|
|
234
|
+
def will_unmount(self):
|
|
235
|
+
super().will_unmount()
|
|
236
|
+
self._state.mounted = False
|
|
237
|
+
self._detach_observable_subscriptions()
|
|
238
|
+
self._run_unmount_effects()
|
|
239
|
+
|
|
240
|
+
def __str__(self):
|
|
241
|
+
return f"{self._c}:{self.fn.__name__}({self._i} - {id(self)})"
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
#
|
|
245
|
+
# Renderer
|
|
246
|
+
#
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class Renderer:
|
|
250
|
+
_ROOT_TOKEN = ("__root__",)
|
|
251
|
+
|
|
252
|
+
def __init__(self, root_component=None):
|
|
253
|
+
self._root_component = root_component
|
|
254
|
+
self._render_stack: list[Component] = []
|
|
255
|
+
self._is_memo = False
|
|
256
|
+
self._contexts: dict[object, list[object]] = defaultdict(list)
|
|
257
|
+
|
|
258
|
+
def set_memo(self):
|
|
259
|
+
self._is_memo = True
|
|
260
|
+
|
|
261
|
+
def push_context(self, key: object, value: object) -> None:
|
|
262
|
+
logger.debug("Renderer._push_context(%s, %s)", key, value)
|
|
263
|
+
self._contexts[key].append(value)
|
|
264
|
+
|
|
265
|
+
def pop_context(self, key: object) -> None:
|
|
266
|
+
logger.debug("Renderer._pop_context(%s)", key)
|
|
267
|
+
stack = self._contexts.get(key)
|
|
268
|
+
if stack:
|
|
269
|
+
stack.pop()
|
|
270
|
+
if not stack:
|
|
271
|
+
del self._contexts[key]
|
|
272
|
+
|
|
273
|
+
def _snapshot_contexts(self) -> dict[object, object]:
|
|
274
|
+
# take top of each stack
|
|
275
|
+
return {k: v[-1] for k, v in self._contexts.items() if v}
|
|
276
|
+
|
|
277
|
+
def with_context(self):
|
|
278
|
+
"""Context manager to make this renderer the 'current' one."""
|
|
279
|
+
|
|
280
|
+
class _C:
|
|
281
|
+
def __init__(_s, r: Renderer):
|
|
282
|
+
_s._tok = None
|
|
283
|
+
_s._r = r
|
|
284
|
+
|
|
285
|
+
def __enter__(_s):
|
|
286
|
+
_s._tok = _CURRENT_RENDERER.set(_s._r)
|
|
287
|
+
return _s._r
|
|
288
|
+
|
|
289
|
+
def __exit__(_s, exc_type, exc, tb):
|
|
290
|
+
if _s._tok is not None:
|
|
291
|
+
_CURRENT_RENDERER.reset(_s._tok)
|
|
292
|
+
|
|
293
|
+
return _C(self)
|
|
294
|
+
|
|
295
|
+
def render(self, root_fn: Callable[..., Any], *args, **kwargs):
|
|
296
|
+
# run with this renderer bound as current
|
|
297
|
+
with self.with_context(), self._Frame(self, self._root_component):
|
|
298
|
+
return root_fn(*args, **kwargs)
|
|
299
|
+
|
|
300
|
+
def render_component(
|
|
301
|
+
self,
|
|
302
|
+
fn: Callable[..., Any],
|
|
303
|
+
args: tuple[Any, ...],
|
|
304
|
+
kwargs: dict[str, Any],
|
|
305
|
+
key=None,
|
|
306
|
+
):
|
|
307
|
+
logger.debug(
|
|
308
|
+
"Renderer._render_component(%s, %s, %s, %s)", fn, args, kwargs, key
|
|
309
|
+
)
|
|
310
|
+
parent_component = len(self._render_stack) and self._render_stack[-1]
|
|
311
|
+
|
|
312
|
+
if not hasattr(fn, "__is_component__"):
|
|
313
|
+
raise ValueError(f"Function {fn} is not a component (missing @component?)")
|
|
314
|
+
|
|
315
|
+
c = Component(
|
|
316
|
+
fn=fn,
|
|
317
|
+
args=args,
|
|
318
|
+
kwargs=kwargs,
|
|
319
|
+
_parent_component=weakref.ref(parent_component)
|
|
320
|
+
if parent_component
|
|
321
|
+
else None,
|
|
322
|
+
memoized=self._is_memo,
|
|
323
|
+
key=key,
|
|
324
|
+
)
|
|
325
|
+
c._contexts = self._snapshot_contexts()
|
|
326
|
+
c._frozen = True
|
|
327
|
+
|
|
328
|
+
self._is_memo = False
|
|
329
|
+
|
|
330
|
+
return c
|
|
331
|
+
|
|
332
|
+
class _Frame:
|
|
333
|
+
"""Context around entering a component; pushes/pops on renderer's stack."""
|
|
334
|
+
|
|
335
|
+
def __init__(self, renderer: Renderer, c: Component | None = None):
|
|
336
|
+
self.r = renderer
|
|
337
|
+
self.c = c
|
|
338
|
+
|
|
339
|
+
def __enter__(self):
|
|
340
|
+
if self.c:
|
|
341
|
+
self.r._render_stack.append(self.c)
|
|
342
|
+
return self.c
|
|
343
|
+
|
|
344
|
+
def __exit__(self, exc_type, exc, tb):
|
|
345
|
+
if self.c:
|
|
346
|
+
self.r._render_stack.pop()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
from typing import Callable, ParamSpec, TypeVar
|
|
3
|
+
|
|
4
|
+
from flet.components.utils import current_renderer
|
|
5
|
+
|
|
6
|
+
P = ParamSpec("P")
|
|
7
|
+
R = TypeVar("R")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def component(fn: Callable[P, R]) -> Callable[P, R]:
|
|
11
|
+
"""
|
|
12
|
+
Marks a function as a component.
|
|
13
|
+
"""
|
|
14
|
+
fn.__is_component__ = True
|
|
15
|
+
|
|
16
|
+
@wraps(fn)
|
|
17
|
+
def component_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
18
|
+
key = kwargs.pop("key", None)
|
|
19
|
+
r = current_renderer()
|
|
20
|
+
return r.render_component(fn, args, kwargs, key=key)
|
|
21
|
+
|
|
22
|
+
component_wrapper.__is_component__ = True
|
|
23
|
+
component_wrapper.__component_impl__ = fn
|
|
24
|
+
return component_wrapper
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import weakref
|
|
2
|
+
from dataclasses import InitVar, dataclass
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from flet.components.component import Component
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass()
|
|
10
|
+
class ComponentOwned:
|
|
11
|
+
owner: InitVar["Component"]
|
|
12
|
+
|
|
13
|
+
def __post_init__(self, owner: "Component") -> None:
|
|
14
|
+
self._component = weakref.ref(owner)
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def component(self) -> Optional["Component"]:
|
|
18
|
+
return self._component()
|
|
19
|
+
|
|
20
|
+
@component.setter
|
|
21
|
+
def component(self, value: "Component") -> None:
|
|
22
|
+
self._component = weakref.ref(value)
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import Any, Callable, ParamSpec, TypeVar
|
|
5
|
+
|
|
6
|
+
from flet.components.hooks.use_memo import use_memo
|
|
7
|
+
|
|
8
|
+
P = ParamSpec("P")
|
|
9
|
+
R = TypeVar("R")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def use_callback(
|
|
13
|
+
fn: Callable[P, R],
|
|
14
|
+
dependencies: Sequence[Any] | None = None,
|
|
15
|
+
) -> Callable[P, R]:
|
|
16
|
+
"""
|
|
17
|
+
Memoize a function identity between renders.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
fn: A function to memoize.
|
|
21
|
+
dependencies: If present, fn is only re-memoized when one of the dependencies
|
|
22
|
+
has changed. If absent, fn is only memoized on initial render.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
A memoized version of the function whose identity is stable between renders.
|
|
26
|
+
"""
|
|
27
|
+
# Just memoize the function object itself
|
|
28
|
+
return use_memo(lambda: fn, dependencies)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import TypeVar, cast
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from typing import Protocol
|
|
7
|
+
except ImportError:
|
|
8
|
+
from typing_extensions import Protocol
|
|
9
|
+
|
|
10
|
+
from flet.components.hooks.hook import Hook
|
|
11
|
+
from flet.components.observable import Observable
|
|
12
|
+
from flet.components.utils import current_component, current_renderer
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ContextHook(Hook):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
ContextValueT = TypeVar("ContextValueT")
|
|
21
|
+
|
|
22
|
+
T = TypeVar("T") # context value type
|
|
23
|
+
ProviderResultT = TypeVar(
|
|
24
|
+
"ProviderResultT", covariant=True
|
|
25
|
+
) # return type of the callback/provider
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ContextProvider(Protocol[T]):
|
|
29
|
+
default_value: T
|
|
30
|
+
_key: object
|
|
31
|
+
|
|
32
|
+
# Generic call: whatever the callback returns (ProviderResultT),
|
|
33
|
+
# the provider returns too.
|
|
34
|
+
def __call__(
|
|
35
|
+
self, value: T, callback: Callable[[], ProviderResultT]
|
|
36
|
+
) -> ProviderResultT: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def create_context(default_value: T) -> ContextProvider[T]:
|
|
40
|
+
"""
|
|
41
|
+
Create a context provider.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
default_value: Default context value when no provider is found.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
A context provider that can be used with `use_context` and to wrap
|
|
48
|
+
parts of the component tree that should share the same context value.
|
|
49
|
+
"""
|
|
50
|
+
key = object()
|
|
51
|
+
|
|
52
|
+
def provider(value: T, callback: Callable[[], ProviderResultT]) -> ProviderResultT: # type: ignore[type-var]
|
|
53
|
+
r = current_renderer()
|
|
54
|
+
r.push_context(key, value)
|
|
55
|
+
try:
|
|
56
|
+
return callback() # type: ignore[return-value]
|
|
57
|
+
finally:
|
|
58
|
+
r.pop_context(key)
|
|
59
|
+
|
|
60
|
+
p = cast(ContextProvider[T], provider)
|
|
61
|
+
p.default_value = default_value
|
|
62
|
+
p._key = key
|
|
63
|
+
return p
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def use_context(context: ContextProvider[T]) -> T:
|
|
67
|
+
"""
|
|
68
|
+
Access the current context value.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
context: A context provider created by `create_context`.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
The current context value, or the default value if no provider is found.
|
|
75
|
+
"""
|
|
76
|
+
component = current_component()
|
|
77
|
+
component.use_hook(lambda: ContextHook(component))
|
|
78
|
+
|
|
79
|
+
value = cast(T, context.default_value)
|
|
80
|
+
# look up the component tree for the nearest context provider
|
|
81
|
+
comp = component
|
|
82
|
+
while comp:
|
|
83
|
+
if context._key in comp._contexts:
|
|
84
|
+
value = cast(T, comp._contexts[context._key])
|
|
85
|
+
break
|
|
86
|
+
comp = comp._parent_component() if comp._parent_component else None
|
|
87
|
+
|
|
88
|
+
if isinstance(value, Observable):
|
|
89
|
+
component._attach_observable_subscription(value)
|
|
90
|
+
|
|
91
|
+
return value
|