pyreact-framework 1.0.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.
- pyreact/__init__.py +144 -0
- pyreact/cli/__init__.py +3 -0
- pyreact/cli/main.py +512 -0
- pyreact/core/__init__.py +80 -0
- pyreact/core/component.py +372 -0
- pyreact/core/context.py +173 -0
- pyreact/core/element.py +208 -0
- pyreact/core/error_boundary.py +145 -0
- pyreact/core/hooks.py +550 -0
- pyreact/core/memo.py +221 -0
- pyreact/core/portal.py +159 -0
- pyreact/core/reconciler.py +399 -0
- pyreact/core/refs.py +213 -0
- pyreact/core/renderer.py +112 -0
- pyreact/core/scheduler.py +304 -0
- pyreact/devtools/__init__.py +18 -0
- pyreact/devtools/debugger.py +314 -0
- pyreact/devtools/profiler.py +288 -0
- pyreact/dom/__init__.py +64 -0
- pyreact/dom/attributes.py +317 -0
- pyreact/dom/dom_operations.py +333 -0
- pyreact/dom/events.py +349 -0
- pyreact/server/__init__.py +34 -0
- pyreact/server/hydration.py +216 -0
- pyreact/server/ssr.py +344 -0
- pyreact/styles/__init__.py +19 -0
- pyreact/styles/css_module.py +231 -0
- pyreact/styles/styled.py +303 -0
- pyreact/testing/__init__.py +71 -0
- pyreact/testing/fire_event.py +355 -0
- pyreact/testing/screen.py +267 -0
- pyreact/testing/test_renderer.py +232 -0
- pyreact/utils/__init__.py +17 -0
- pyreact/utils/diff.py +182 -0
- pyreact/utils/object_pool.py +216 -0
- pyreact_framework-1.0.0.dist-info/METADATA +363 -0
- pyreact_framework-1.0.0.dist-info/RECORD +41 -0
- pyreact_framework-1.0.0.dist-info/WHEEL +5 -0
- pyreact_framework-1.0.0.dist-info/entry_points.txt +2 -0
- pyreact_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
- pyreact_framework-1.0.0.dist-info/top_level.txt +1 -0
pyreact/core/__init__.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyReact Core Module
|
|
3
|
+
===================
|
|
4
|
+
|
|
5
|
+
This module contains the core functionality of PyReact framework:
|
|
6
|
+
- VNode: Virtual DOM node representation
|
|
7
|
+
- Component: Base class for stateful components
|
|
8
|
+
- h(): Hyperscript function for creating elements
|
|
9
|
+
- Reconciler: Diff algorithm for efficient updates
|
|
10
|
+
- Renderer: DOM rendering engine
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .element import VNode, h, create_element
|
|
14
|
+
from .component import Component
|
|
15
|
+
from .renderer import render, hydrate, create_root
|
|
16
|
+
from .reconciler import Reconciler
|
|
17
|
+
from .hooks import (
|
|
18
|
+
use_state,
|
|
19
|
+
use_reducer,
|
|
20
|
+
use_effect,
|
|
21
|
+
use_layout_effect,
|
|
22
|
+
use_context,
|
|
23
|
+
use_ref,
|
|
24
|
+
use_memo,
|
|
25
|
+
use_callback,
|
|
26
|
+
use_imperative_handle,
|
|
27
|
+
use_debug_value,
|
|
28
|
+
use_id,
|
|
29
|
+
use_transition,
|
|
30
|
+
use_deferred_value,
|
|
31
|
+
)
|
|
32
|
+
from .context import create_context
|
|
33
|
+
from .refs import create_ref, forward_ref
|
|
34
|
+
from .portal import create_portal
|
|
35
|
+
from .memo import memo, lazy
|
|
36
|
+
from .error_boundary import ErrorBoundary
|
|
37
|
+
from .scheduler import Scheduler
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
# Element
|
|
41
|
+
'VNode',
|
|
42
|
+
'h',
|
|
43
|
+
'create_element',
|
|
44
|
+
# Component
|
|
45
|
+
'Component',
|
|
46
|
+
# Renderer
|
|
47
|
+
'render',
|
|
48
|
+
'hydrate',
|
|
49
|
+
'create_root',
|
|
50
|
+
# Reconciler
|
|
51
|
+
'Reconciler',
|
|
52
|
+
# Hooks
|
|
53
|
+
'use_state',
|
|
54
|
+
'use_reducer',
|
|
55
|
+
'use_effect',
|
|
56
|
+
'use_layout_effect',
|
|
57
|
+
'use_context',
|
|
58
|
+
'use_ref',
|
|
59
|
+
'use_memo',
|
|
60
|
+
'use_callback',
|
|
61
|
+
'use_imperative_handle',
|
|
62
|
+
'use_debug_value',
|
|
63
|
+
'use_id',
|
|
64
|
+
'use_transition',
|
|
65
|
+
'use_deferred_value',
|
|
66
|
+
# Context
|
|
67
|
+
'create_context',
|
|
68
|
+
# Refs
|
|
69
|
+
'create_ref',
|
|
70
|
+
'forward_ref',
|
|
71
|
+
# Portal
|
|
72
|
+
'create_portal',
|
|
73
|
+
# Memo
|
|
74
|
+
'memo',
|
|
75
|
+
'lazy',
|
|
76
|
+
# Error Boundary
|
|
77
|
+
'ErrorBoundary',
|
|
78
|
+
# Scheduler
|
|
79
|
+
'Scheduler',
|
|
80
|
+
]
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Component Base Class Module
|
|
3
|
+
===========================
|
|
4
|
+
|
|
5
|
+
This module defines the base Component class for stateful components
|
|
6
|
+
in PyReact, similar to React's React.Component.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Callable, Dict, List, Optional, Union, TYPE_CHECKING
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
import weakref
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .element import VNode
|
|
15
|
+
from .renderer import Root
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Component(ABC):
|
|
19
|
+
"""
|
|
20
|
+
Base class for stateful components
|
|
21
|
+
|
|
22
|
+
Components are the building blocks of PyReact applications.
|
|
23
|
+
They manage their own state and define how to render themselves.
|
|
24
|
+
|
|
25
|
+
Lifecycle Methods:
|
|
26
|
+
- component_will_mount(): Called before mounting (deprecated)
|
|
27
|
+
- component_did_mount(): Called after mounting
|
|
28
|
+
- component_will_receive_props(): Called when props change (deprecated)
|
|
29
|
+
- should_component_update(): Return False to prevent re-render
|
|
30
|
+
- component_will_update(): Called before update (deprecated)
|
|
31
|
+
- component_did_update(): Called after update
|
|
32
|
+
- component_will_unmount(): Called before unmounting
|
|
33
|
+
- component_did_catch(): Called when child throws error
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
class Counter(Component):
|
|
37
|
+
def __init__(self, props):
|
|
38
|
+
super().__init__(props)
|
|
39
|
+
self.state = {'count': 0}
|
|
40
|
+
|
|
41
|
+
def increment(self, event):
|
|
42
|
+
self.set_state({'count': self.state['count'] + 1})
|
|
43
|
+
|
|
44
|
+
def render(self):
|
|
45
|
+
return h('div', {'className': 'counter'},
|
|
46
|
+
h('span', None, f"Count: {self.state['count']}"),
|
|
47
|
+
h('button', {'onClick': self.increment}, '+1')
|
|
48
|
+
)
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, props: Optional[Dict[str, Any]] = None):
|
|
52
|
+
"""
|
|
53
|
+
Initialize component
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
props: Properties passed from parent component
|
|
57
|
+
"""
|
|
58
|
+
self.props: Dict[str, Any] = props or {}
|
|
59
|
+
self.state: Dict[str, Any] = {}
|
|
60
|
+
self.context: Dict[str, Any] = {}
|
|
61
|
+
|
|
62
|
+
# Internal state
|
|
63
|
+
self._pending_state: Optional[Dict[str, Any]] = None
|
|
64
|
+
self._pending_callbacks: List[Callable] = []
|
|
65
|
+
self._is_mounted: bool = False
|
|
66
|
+
self._is_rendering: bool = False
|
|
67
|
+
self._root: Optional[weakref.ref] = None
|
|
68
|
+
self._vnode: Optional['VNode'] = None
|
|
69
|
+
self._dom_node: Optional[Any] = None
|
|
70
|
+
self._updater: Optional[Callable] = None
|
|
71
|
+
self._force_update: bool = False
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def render(self) -> 'VNode':
|
|
75
|
+
"""
|
|
76
|
+
Render the component
|
|
77
|
+
|
|
78
|
+
Must be implemented by subclasses.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
VNode: Virtual DOM node representing the component's output
|
|
82
|
+
"""
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
def set_state(
|
|
86
|
+
self,
|
|
87
|
+
new_state: Union[Dict[str, Any], Callable[[Dict[str, Any]], Dict[str, Any]]],
|
|
88
|
+
callback: Optional[Callable] = None
|
|
89
|
+
) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Update component state
|
|
92
|
+
|
|
93
|
+
Triggers a re-render after state update.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
new_state: New state dict or function that returns new state
|
|
97
|
+
callback: Optional callback called after update
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
# Direct state update
|
|
101
|
+
self.set_state({'count': 1})
|
|
102
|
+
|
|
103
|
+
# Functional update
|
|
104
|
+
self.set_state(lambda state: {'count': state['count'] + 1})
|
|
105
|
+
"""
|
|
106
|
+
if self._is_rendering:
|
|
107
|
+
# Queue update for after render
|
|
108
|
+
if callable(new_state):
|
|
109
|
+
new_state = new_state(self.state)
|
|
110
|
+
self._pending_state = {**self.state, **new_state}
|
|
111
|
+
if callback:
|
|
112
|
+
self._pending_callbacks.append(callback)
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
# Compute new state
|
|
116
|
+
if callable(new_state):
|
|
117
|
+
new_state = new_state(self.state)
|
|
118
|
+
|
|
119
|
+
# Merge with pending state if exists
|
|
120
|
+
if self._pending_state:
|
|
121
|
+
self._pending_state = {**self._pending_state, **new_state}
|
|
122
|
+
else:
|
|
123
|
+
self._pending_state = {**self.state, **new_state}
|
|
124
|
+
|
|
125
|
+
if callback:
|
|
126
|
+
self._pending_callbacks.append(callback)
|
|
127
|
+
|
|
128
|
+
# Schedule update
|
|
129
|
+
self._schedule_update()
|
|
130
|
+
|
|
131
|
+
def force_update(self, callback: Optional[Callable] = None) -> None:
|
|
132
|
+
"""
|
|
133
|
+
Force a re-render of the component
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
callback: Optional callback called after update
|
|
137
|
+
"""
|
|
138
|
+
self._force_update = True
|
|
139
|
+
if callback:
|
|
140
|
+
self._pending_callbacks.append(callback)
|
|
141
|
+
self._schedule_update()
|
|
142
|
+
|
|
143
|
+
def _schedule_update(self) -> None:
|
|
144
|
+
"""Schedule an update with the renderer"""
|
|
145
|
+
if self._updater:
|
|
146
|
+
self._updater(self)
|
|
147
|
+
|
|
148
|
+
def _apply_state(self) -> bool:
|
|
149
|
+
"""
|
|
150
|
+
Apply pending state changes
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
bool: True if state was applied
|
|
154
|
+
"""
|
|
155
|
+
if self._pending_state:
|
|
156
|
+
self.state = self._pending_state
|
|
157
|
+
self._pending_state = None
|
|
158
|
+
return True
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
def _run_callbacks(self) -> None:
|
|
162
|
+
"""Run pending callbacks after update"""
|
|
163
|
+
callbacks = self._pending_callbacks.copy()
|
|
164
|
+
self._pending_callbacks.clear()
|
|
165
|
+
for callback in callbacks:
|
|
166
|
+
callback()
|
|
167
|
+
|
|
168
|
+
# Lifecycle Methods
|
|
169
|
+
|
|
170
|
+
def component_will_mount(self) -> None:
|
|
171
|
+
"""
|
|
172
|
+
Called before the component is mounted to the DOM
|
|
173
|
+
|
|
174
|
+
Deprecated: Use component_did_mount instead
|
|
175
|
+
"""
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
def component_did_mount(self) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Called after the component is mounted to the DOM
|
|
181
|
+
|
|
182
|
+
Use this for:
|
|
183
|
+
- Fetching data
|
|
184
|
+
- Setting up subscriptions
|
|
185
|
+
- DOM manipulation
|
|
186
|
+
"""
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
def component_will_receive_props(self, next_props: Dict[str, Any]) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Called before new props are received
|
|
192
|
+
|
|
193
|
+
Deprecated: Use get_derived_state_from_props instead
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
next_props: The new props
|
|
197
|
+
"""
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def get_derived_state_from_props(
|
|
202
|
+
props: Dict[str, Any],
|
|
203
|
+
state: Dict[str, Any]
|
|
204
|
+
) -> Optional[Dict[str, Any]]:
|
|
205
|
+
"""
|
|
206
|
+
Called before render with new props
|
|
207
|
+
|
|
208
|
+
Return an object to update state, or None to not update.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
props: New props
|
|
212
|
+
state: Current state
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
dict or None: State updates
|
|
216
|
+
"""
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
def should_component_update(
|
|
220
|
+
self,
|
|
221
|
+
next_props: Dict[str, Any],
|
|
222
|
+
next_state: Dict[str, Any]
|
|
223
|
+
) -> bool:
|
|
224
|
+
"""
|
|
225
|
+
Called before re-render to determine if update should proceed
|
|
226
|
+
|
|
227
|
+
Override to implement custom comparison logic.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
next_props: New props
|
|
231
|
+
next_state: New state
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
bool: True to proceed with update
|
|
235
|
+
"""
|
|
236
|
+
return True
|
|
237
|
+
|
|
238
|
+
def component_will_update(
|
|
239
|
+
self,
|
|
240
|
+
next_props: Dict[str, Any],
|
|
241
|
+
next_state: Dict[str, Any]
|
|
242
|
+
) -> None:
|
|
243
|
+
"""
|
|
244
|
+
Called before re-render
|
|
245
|
+
|
|
246
|
+
Deprecated: Use component_did_update instead
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
next_props: New props
|
|
250
|
+
next_state: New state
|
|
251
|
+
"""
|
|
252
|
+
pass
|
|
253
|
+
|
|
254
|
+
def component_did_update(
|
|
255
|
+
self,
|
|
256
|
+
prev_props: Dict[str, Any],
|
|
257
|
+
prev_state: Dict[str, Any]
|
|
258
|
+
) -> None:
|
|
259
|
+
"""
|
|
260
|
+
Called after re-render
|
|
261
|
+
|
|
262
|
+
Use for:
|
|
263
|
+
- DOM operations after update
|
|
264
|
+
- Network requests (compare props first)
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
prev_props: Previous props
|
|
268
|
+
prev_state: Previous state
|
|
269
|
+
"""
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
def component_will_unmount(self) -> None:
|
|
273
|
+
"""
|
|
274
|
+
Called before the component is unmounted
|
|
275
|
+
|
|
276
|
+
Use for:
|
|
277
|
+
- Cleaning up subscriptions
|
|
278
|
+
- Canceling network requests
|
|
279
|
+
- Invalidating timers
|
|
280
|
+
"""
|
|
281
|
+
pass
|
|
282
|
+
|
|
283
|
+
def component_did_catch(self, error: Exception, info: Dict[str, Any]) -> None:
|
|
284
|
+
"""
|
|
285
|
+
Called when a child component throws an error
|
|
286
|
+
|
|
287
|
+
Use for logging error information.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
error: The error that was thrown
|
|
291
|
+
info: Object with componentStack key
|
|
292
|
+
"""
|
|
293
|
+
pass
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def get_derived_state_from_error(error: Exception) -> Optional[Dict[str, Any]]:
|
|
297
|
+
"""
|
|
298
|
+
Called when a child component throws an error
|
|
299
|
+
|
|
300
|
+
Return an object to update state.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
error: The error that was thrown
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
dict or None: State updates
|
|
307
|
+
"""
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
def __repr__(self) -> str:
|
|
311
|
+
return f"<{self.__class__.__name__}>"
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class PureComponent(Component):
|
|
315
|
+
"""
|
|
316
|
+
Component that implements should_component_update with shallow comparison
|
|
317
|
+
|
|
318
|
+
Use PureComponent when you want to prevent unnecessary re-renders
|
|
319
|
+
when props and state haven't changed.
|
|
320
|
+
|
|
321
|
+
Example:
|
|
322
|
+
class UserAvatar(PureComponent):
|
|
323
|
+
def render(self):
|
|
324
|
+
return h('img', {
|
|
325
|
+
'src': self.props['user']['avatar_url'],
|
|
326
|
+
'alt': self.props['user']['name']
|
|
327
|
+
})
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
def should_component_update(
|
|
331
|
+
self,
|
|
332
|
+
next_props: Dict[str, Any],
|
|
333
|
+
next_state: Dict[str, Any]
|
|
334
|
+
) -> bool:
|
|
335
|
+
"""
|
|
336
|
+
Only re-render if props or state have changed (shallow comparison)
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
next_props: New props
|
|
340
|
+
next_state: New state
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
bool: True if props or state changed
|
|
344
|
+
"""
|
|
345
|
+
return (
|
|
346
|
+
not shallow_equal(self.props, next_props) or
|
|
347
|
+
not shallow_equal(self.state, next_state)
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def shallow_equal(obj1: Dict[str, Any], obj2: Dict[str, Any]) -> bool:
|
|
352
|
+
"""
|
|
353
|
+
Perform shallow comparison of two objects
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
obj1: First object
|
|
357
|
+
obj2: Second object
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
bool: True if objects are shallowly equal
|
|
361
|
+
"""
|
|
362
|
+
if obj1 is obj2:
|
|
363
|
+
return True
|
|
364
|
+
|
|
365
|
+
if len(obj1) != len(obj2):
|
|
366
|
+
return False
|
|
367
|
+
|
|
368
|
+
for key in obj1:
|
|
369
|
+
if key not in obj2 or obj1[key] != obj2[key]:
|
|
370
|
+
return False
|
|
371
|
+
|
|
372
|
+
return True
|
pyreact/core/context.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context API Module
|
|
3
|
+
==================
|
|
4
|
+
|
|
5
|
+
This module implements the Context API for sharing state across components
|
|
6
|
+
without prop drilling.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
10
|
+
import uuid
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Context:
|
|
14
|
+
"""
|
|
15
|
+
Context object for sharing state
|
|
16
|
+
|
|
17
|
+
Created by create_context() function.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
_id: Unique identifier for this context
|
|
21
|
+
_default_value: Default value when no Provider is found
|
|
22
|
+
_providers: Stack of provider values
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, default_value: Any = None):
|
|
26
|
+
self._id = f"context-{uuid.uuid4().hex[:8]}"
|
|
27
|
+
self._default_value = default_value
|
|
28
|
+
self._providers: Dict[int, Any] = {}
|
|
29
|
+
self._provider_stack: List[int] = []
|
|
30
|
+
|
|
31
|
+
def _get_value(self) -> Any:
|
|
32
|
+
"""Get current context value"""
|
|
33
|
+
if self._provider_stack:
|
|
34
|
+
provider_id = self._provider_stack[-1]
|
|
35
|
+
return self._providers.get(provider_id, self._default_value)
|
|
36
|
+
return self._default_value
|
|
37
|
+
|
|
38
|
+
def _push_provider(self, provider_id: int, value: Any) -> None:
|
|
39
|
+
"""Push a provider value onto the stack"""
|
|
40
|
+
self._providers[provider_id] = value
|
|
41
|
+
self._provider_stack.append(provider_id)
|
|
42
|
+
|
|
43
|
+
def _pop_provider(self, provider_id: int) -> None:
|
|
44
|
+
"""Pop a provider value from the stack"""
|
|
45
|
+
if provider_id in self._providers:
|
|
46
|
+
del self._providers[provider_id]
|
|
47
|
+
if provider_id in self._provider_stack:
|
|
48
|
+
self._provider_stack.remove(provider_id)
|
|
49
|
+
|
|
50
|
+
class Provider:
|
|
51
|
+
"""
|
|
52
|
+
Provider component for context
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
h(ThemeContext.Provider, {'value': 'dark'},
|
|
56
|
+
h(App, None)
|
|
57
|
+
)
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, context: 'Context', props: Dict[str, Any]):
|
|
61
|
+
self.context = context
|
|
62
|
+
self.props = props
|
|
63
|
+
self._id = id(self)
|
|
64
|
+
self._children = props.get('children', [])
|
|
65
|
+
|
|
66
|
+
def render(self) -> Any:
|
|
67
|
+
"""Provider doesn't render anything, just provides value"""
|
|
68
|
+
return self._children
|
|
69
|
+
|
|
70
|
+
def __enter__(self):
|
|
71
|
+
"""Context manager support"""
|
|
72
|
+
value = self.props.get('value', self.context._default_value)
|
|
73
|
+
self.context._push_provider(self._id, value)
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
def __exit__(self, *args):
|
|
77
|
+
"""Context manager support"""
|
|
78
|
+
self.context._pop_provider(self._id)
|
|
79
|
+
|
|
80
|
+
class Consumer:
|
|
81
|
+
"""
|
|
82
|
+
Consumer component for context
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
h(ThemeContext.Consumer, None,
|
|
86
|
+
lambda theme: h('div', None, f"Theme: {theme}")
|
|
87
|
+
)
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(self, context: 'Context', props: Dict[str, Any]):
|
|
91
|
+
self.context = context
|
|
92
|
+
self.props = props
|
|
93
|
+
|
|
94
|
+
def render(self) -> Any:
|
|
95
|
+
"""Call children function with context value"""
|
|
96
|
+
children = self.props.get('children')
|
|
97
|
+
if callable(children):
|
|
98
|
+
return children(self.context._get_value())
|
|
99
|
+
return children
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def create_context(default_value: Any = None) -> Context:
|
|
103
|
+
"""
|
|
104
|
+
Create a new Context object
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
default_value: Default value when no Provider is found
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Context: The created context
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
ThemeContext = create_context('light')
|
|
114
|
+
|
|
115
|
+
def App():
|
|
116
|
+
return h(ThemeContext.Provider, {'value': 'dark'},
|
|
117
|
+
h(Toolbar, None)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def Toolbar():
|
|
121
|
+
theme = use_context(ThemeContext)
|
|
122
|
+
return h('div', {'className': f"toolbar-{theme}"}, '...')
|
|
123
|
+
"""
|
|
124
|
+
return Context(default_value)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def use_context(context: Context) -> Any:
|
|
128
|
+
"""
|
|
129
|
+
Hook to consume context value
|
|
130
|
+
|
|
131
|
+
This is a re-export from hooks.py for convenience.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
context: Context object created by create_context()
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Current context value
|
|
138
|
+
"""
|
|
139
|
+
from .hooks import use_context as _use_context
|
|
140
|
+
return _use_context(context)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class ContextManager:
|
|
144
|
+
"""
|
|
145
|
+
Manager for all contexts in the application
|
|
146
|
+
|
|
147
|
+
Handles nested providers and context updates.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
def __init__(self):
|
|
151
|
+
self._contexts: Dict[str, Context] = {}
|
|
152
|
+
|
|
153
|
+
def register(self, context: Context) -> None:
|
|
154
|
+
"""Register a context"""
|
|
155
|
+
self._contexts[context._id] = context
|
|
156
|
+
|
|
157
|
+
def unregister(self, context: Context) -> None:
|
|
158
|
+
"""Unregister a context"""
|
|
159
|
+
if context._id in self._contexts:
|
|
160
|
+
del self._contexts[context._id]
|
|
161
|
+
|
|
162
|
+
def get_context(self, context_id: str) -> Optional[Context]:
|
|
163
|
+
"""Get a context by ID"""
|
|
164
|
+
return self._contexts.get(context_id)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# Global context manager
|
|
168
|
+
_context_manager = ContextManager()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def get_context_manager() -> ContextManager:
|
|
172
|
+
"""Get the global context manager"""
|
|
173
|
+
return _context_manager
|