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/hooks.py
ADDED
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hooks Module
|
|
3
|
+
============
|
|
4
|
+
|
|
5
|
+
This module implements React-style hooks for functional components.
|
|
6
|
+
Hooks allow you to use state and other React features without writing a class.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
10
|
+
from functools import wraps
|
|
11
|
+
import uuid
|
|
12
|
+
|
|
13
|
+
# Global context for hooks
|
|
14
|
+
_current_component: Optional[Any] = None
|
|
15
|
+
_hook_index: int = 0
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_current_component() -> Any:
|
|
19
|
+
"""Get the current component context"""
|
|
20
|
+
global _current_component
|
|
21
|
+
if _current_component is None:
|
|
22
|
+
raise RuntimeError('Hooks can only be called inside a component')
|
|
23
|
+
return _current_component
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _set_current_component(component: Any) -> None:
|
|
27
|
+
"""Set the current component context"""
|
|
28
|
+
global _current_component, _hook_index
|
|
29
|
+
_current_component = component
|
|
30
|
+
_hook_index = 0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _reset_hook_index() -> None:
|
|
34
|
+
"""Reset hook index for new render"""
|
|
35
|
+
global _hook_index
|
|
36
|
+
_hook_index = 0
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def use_state(
|
|
40
|
+
initial_value: Union[Any, Callable[[], Any]]
|
|
41
|
+
) -> Tuple[Any, Callable[[Any], None]]:
|
|
42
|
+
"""
|
|
43
|
+
Hook for state management
|
|
44
|
+
|
|
45
|
+
Returns a stateful value and a function to update it.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
initial_value: Initial state value or function that returns initial value
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
tuple: (current_value, setter_function)
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
def Counter(props):
|
|
55
|
+
count, set_count = use_state(0)
|
|
56
|
+
return h('div', None,
|
|
57
|
+
h('span', None, f"Count: {count}"),
|
|
58
|
+
h('button', {'onClick': lambda _: set_count(count + 1)}, '+')
|
|
59
|
+
)
|
|
60
|
+
"""
|
|
61
|
+
global _hook_index
|
|
62
|
+
component = _get_current_component()
|
|
63
|
+
hook_index = _hook_index
|
|
64
|
+
|
|
65
|
+
# Initialize hook if needed
|
|
66
|
+
if hook_index >= len(component._hooks):
|
|
67
|
+
initial = initial_value() if callable(initial_value) else initial_value
|
|
68
|
+
component._hooks.append({
|
|
69
|
+
'value': initial,
|
|
70
|
+
'type': 'state'
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
hook = component._hooks[hook_index]
|
|
74
|
+
_hook_index += 1
|
|
75
|
+
|
|
76
|
+
def set_state(new_value: Union[Any, Callable[[Any], Any]]) -> None:
|
|
77
|
+
"""Update state value"""
|
|
78
|
+
if callable(new_value):
|
|
79
|
+
hook['value'] = new_value(hook['value'])
|
|
80
|
+
else:
|
|
81
|
+
hook['value'] = new_value
|
|
82
|
+
|
|
83
|
+
# Trigger re-render
|
|
84
|
+
if hasattr(component, '_schedule_update'):
|
|
85
|
+
component._schedule_update()
|
|
86
|
+
|
|
87
|
+
return hook['value'], set_state
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def use_reducer(
|
|
91
|
+
reducer: Callable[[Any, Any], Any],
|
|
92
|
+
initial_state: Any,
|
|
93
|
+
init: Optional[Callable[[Any], Any]] = None
|
|
94
|
+
) -> Tuple[Any, Callable[[Any], None]]:
|
|
95
|
+
"""
|
|
96
|
+
Hook for complex state management with reducer
|
|
97
|
+
|
|
98
|
+
Similar to Redux pattern.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
reducer: Function (state, action) -> new_state
|
|
102
|
+
initial_state: Initial state value
|
|
103
|
+
init: Optional function to compute initial state lazily
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
tuple: (state, dispatch_function)
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
def reducer(state, action):
|
|
110
|
+
if action['type'] == 'INCREMENT':
|
|
111
|
+
return {'count': state['count'] + 1}
|
|
112
|
+
elif action['type'] == 'DECREMENT':
|
|
113
|
+
return {'count': state['count'] - 1}
|
|
114
|
+
return state
|
|
115
|
+
|
|
116
|
+
def Counter(props):
|
|
117
|
+
state, dispatch = use_reducer(reducer, {'count': 0})
|
|
118
|
+
return h('div', None,
|
|
119
|
+
h('span', None, f"Count: {state['count']}"),
|
|
120
|
+
h('button', {'onClick': lambda _: dispatch({'type': 'INCREMENT'})}, '+')
|
|
121
|
+
)
|
|
122
|
+
"""
|
|
123
|
+
global _hook_index
|
|
124
|
+
component = _get_current_component()
|
|
125
|
+
hook_index = _hook_index
|
|
126
|
+
|
|
127
|
+
# Initialize hook if needed
|
|
128
|
+
if hook_index >= len(component._hooks):
|
|
129
|
+
initial = init(initial_state) if init else initial_state
|
|
130
|
+
component._hooks.append({
|
|
131
|
+
'value': initial,
|
|
132
|
+
'type': 'reducer',
|
|
133
|
+
'reducer': reducer
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
hook = component._hooks[hook_index]
|
|
137
|
+
_hook_index += 1
|
|
138
|
+
|
|
139
|
+
def dispatch(action: Any) -> None:
|
|
140
|
+
"""Dispatch an action to the reducer"""
|
|
141
|
+
hook['value'] = hook['reducer'](hook['value'], action)
|
|
142
|
+
if hasattr(component, '_schedule_update'):
|
|
143
|
+
component._schedule_update()
|
|
144
|
+
|
|
145
|
+
return hook['value'], dispatch
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def use_effect(
|
|
149
|
+
setup: Callable[[], Optional[Callable[[], None]]],
|
|
150
|
+
dependencies: Optional[List[Any]] = None
|
|
151
|
+
) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Hook for side effects
|
|
154
|
+
|
|
155
|
+
Runs after render. Return a cleanup function to run before next effect or unmount.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
setup: Function that returns cleanup function or None
|
|
159
|
+
dependencies: List of values that trigger effect when changed
|
|
160
|
+
|
|
161
|
+
Example:
|
|
162
|
+
def Timer(props):
|
|
163
|
+
seconds, set_seconds = use_state(0)
|
|
164
|
+
|
|
165
|
+
@use_effect([])
|
|
166
|
+
def setup_timer():
|
|
167
|
+
def tick():
|
|
168
|
+
set_seconds(lambda s: s + 1)
|
|
169
|
+
interval_id = setInterval(tick, 1000)
|
|
170
|
+
return lambda: clearInterval(interval_id)
|
|
171
|
+
|
|
172
|
+
return h('div', None, f"Seconds: {seconds}")
|
|
173
|
+
"""
|
|
174
|
+
global _hook_index
|
|
175
|
+
component = _get_current_component()
|
|
176
|
+
hook_index = _hook_index
|
|
177
|
+
|
|
178
|
+
# Initialize hook if needed
|
|
179
|
+
if hook_index >= len(component._hooks):
|
|
180
|
+
component._hooks.append({
|
|
181
|
+
'type': 'effect',
|
|
182
|
+
'cleanup': None,
|
|
183
|
+
'deps': None
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
hook = component._hooks[hook_index]
|
|
187
|
+
_hook_index += 1
|
|
188
|
+
|
|
189
|
+
# Check if dependencies changed
|
|
190
|
+
deps_changed = (
|
|
191
|
+
hook['deps'] is None or
|
|
192
|
+
dependencies is None or
|
|
193
|
+
_deps_changed(hook['deps'], dependencies)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if deps_changed:
|
|
197
|
+
# Run cleanup from previous effect
|
|
198
|
+
if hook['cleanup']:
|
|
199
|
+
try:
|
|
200
|
+
hook['cleanup']()
|
|
201
|
+
except Exception:
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
# Run new setup
|
|
205
|
+
hook['cleanup'] = setup()
|
|
206
|
+
hook['deps'] = dependencies.copy() if dependencies else None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def use_layout_effect(
|
|
210
|
+
setup: Callable[[], Optional[Callable[[], None]]],
|
|
211
|
+
dependencies: Optional[List[Any]] = None
|
|
212
|
+
) -> None:
|
|
213
|
+
"""
|
|
214
|
+
Hook for synchronous effects after DOM mutations
|
|
215
|
+
|
|
216
|
+
Similar to use_effect but fires synchronously after all DOM mutations.
|
|
217
|
+
Use for DOM measurements or mutations.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
setup: Function that returns cleanup function or None
|
|
221
|
+
dependencies: List of values that trigger effect when changed
|
|
222
|
+
"""
|
|
223
|
+
# In this implementation, use_layout_effect behaves the same as use_effect
|
|
224
|
+
# A real implementation would schedule this to run synchronously after paint
|
|
225
|
+
use_effect(setup, dependencies)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def use_context(context: Any) -> Any:
|
|
229
|
+
"""
|
|
230
|
+
Hook to consume context value
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
context: Context object created by create_context()
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Current context value
|
|
237
|
+
|
|
238
|
+
Example:
|
|
239
|
+
def Button(props):
|
|
240
|
+
theme = use_context(ThemeContext)
|
|
241
|
+
return h('button', {'className': f"btn-{theme}"}, 'Click')
|
|
242
|
+
"""
|
|
243
|
+
global _hook_index
|
|
244
|
+
component = _get_current_component()
|
|
245
|
+
hook_index = _hook_index
|
|
246
|
+
|
|
247
|
+
# Initialize hook if needed
|
|
248
|
+
if hook_index >= len(component._hooks):
|
|
249
|
+
component._hooks.append({
|
|
250
|
+
'type': 'context',
|
|
251
|
+
'context': context,
|
|
252
|
+
'value': context._default_value
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
hook = component._hooks[hook_index]
|
|
256
|
+
_hook_index += 1
|
|
257
|
+
|
|
258
|
+
# Get current context value
|
|
259
|
+
return hook['context']._get_value() if hasattr(hook['context'], '_get_value') else hook['value']
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def use_ref(initial_value: Any = None) -> 'Ref':
|
|
263
|
+
"""
|
|
264
|
+
Hook for mutable reference that persists across renders
|
|
265
|
+
|
|
266
|
+
Changing ref.current does NOT trigger re-render.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
initial_value: Initial value for ref.current
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Ref object with .current property
|
|
273
|
+
|
|
274
|
+
Example:
|
|
275
|
+
def TextInput(props):
|
|
276
|
+
input_ref = use_ref(None)
|
|
277
|
+
|
|
278
|
+
def focus():
|
|
279
|
+
input_ref.current.focus()
|
|
280
|
+
|
|
281
|
+
return h('input', {'ref': input_ref, 'type': 'text'})
|
|
282
|
+
"""
|
|
283
|
+
global _hook_index
|
|
284
|
+
component = _get_current_component()
|
|
285
|
+
hook_index = _hook_index
|
|
286
|
+
|
|
287
|
+
# Initialize hook if needed
|
|
288
|
+
if hook_index >= len(component._hooks):
|
|
289
|
+
ref = Ref()
|
|
290
|
+
ref.current = initial_value
|
|
291
|
+
component._hooks.append({
|
|
292
|
+
'type': 'ref',
|
|
293
|
+
'ref': ref
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
hook = component._hooks[hook_index]
|
|
297
|
+
_hook_index += 1
|
|
298
|
+
|
|
299
|
+
return hook['ref']
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def use_memo(factory: Callable[[], Any], dependencies: List[Any]) -> Any:
|
|
303
|
+
"""
|
|
304
|
+
Hook to memoize expensive computations
|
|
305
|
+
|
|
306
|
+
Only recomputes when dependencies change.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
factory: Function that computes the value
|
|
310
|
+
dependencies: List of values that trigger recomputation
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
Memoized value
|
|
314
|
+
|
|
315
|
+
Example:
|
|
316
|
+
def ExpensiveComponent(props):
|
|
317
|
+
result = use_memo(
|
|
318
|
+
lambda: expensive_computation(props['data']),
|
|
319
|
+
[props['data']]
|
|
320
|
+
)
|
|
321
|
+
return h('div', None, result)
|
|
322
|
+
"""
|
|
323
|
+
global _hook_index
|
|
324
|
+
component = _get_current_component()
|
|
325
|
+
hook_index = _hook_index
|
|
326
|
+
|
|
327
|
+
# Initialize hook if needed
|
|
328
|
+
if hook_index >= len(component._hooks):
|
|
329
|
+
component._hooks.append({
|
|
330
|
+
'type': 'memo',
|
|
331
|
+
'value': factory(),
|
|
332
|
+
'deps': dependencies.copy()
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
hook = component._hooks[hook_index]
|
|
336
|
+
_hook_index += 1
|
|
337
|
+
|
|
338
|
+
# Check if dependencies changed
|
|
339
|
+
if _deps_changed(hook['deps'], dependencies):
|
|
340
|
+
hook['value'] = factory()
|
|
341
|
+
hook['deps'] = dependencies.copy()
|
|
342
|
+
|
|
343
|
+
return hook['value']
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def use_callback(callback: Callable, dependencies: List[Any]) -> Callable:
|
|
347
|
+
"""
|
|
348
|
+
Hook to memoize callbacks
|
|
349
|
+
|
|
350
|
+
Returns a memoized callback that only changes when dependencies change.
|
|
351
|
+
Useful for passing callbacks to optimized child components.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
callback: Function to memoize
|
|
355
|
+
dependencies: List of values that trigger callback update
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Memoized callback
|
|
359
|
+
|
|
360
|
+
Example:
|
|
361
|
+
def Parent(props):
|
|
362
|
+
def handle_click(item):
|
|
363
|
+
print(f"Clicked {item}")
|
|
364
|
+
|
|
365
|
+
memoized_click = use_callback(handle_click, [])
|
|
366
|
+
return h(Child, {'onClick': memoized_click})
|
|
367
|
+
"""
|
|
368
|
+
return use_memo(lambda: callback, dependencies)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def use_imperative_handle(
|
|
372
|
+
ref: 'Ref',
|
|
373
|
+
create_handle: Callable[[], Any],
|
|
374
|
+
dependencies: Optional[List[Any]] = None
|
|
375
|
+
) -> None:
|
|
376
|
+
"""
|
|
377
|
+
Hook to customize the instance value exposed to parent components
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
ref: Ref passed from parent
|
|
381
|
+
create_handle: Function that returns object to expose
|
|
382
|
+
dependencies: List of values that trigger handle update
|
|
383
|
+
|
|
384
|
+
Example:
|
|
385
|
+
def FancyInput(props, ref):
|
|
386
|
+
input_ref = use_ref(None)
|
|
387
|
+
|
|
388
|
+
use_imperative_handle(ref, lambda: {
|
|
389
|
+
'focus': lambda: input_ref.current.focus(),
|
|
390
|
+
'scrollIntoView': lambda: input_ref.current.scrollIntoView()
|
|
391
|
+
}, [])
|
|
392
|
+
|
|
393
|
+
return h('input', {'ref': input_ref})
|
|
394
|
+
"""
|
|
395
|
+
def effect():
|
|
396
|
+
if ref:
|
|
397
|
+
ref.current = create_handle()
|
|
398
|
+
return lambda: None
|
|
399
|
+
|
|
400
|
+
use_effect(effect, dependencies)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def use_debug_value(value: Any, formatter: Optional[Callable[[Any], str]] = None) -> None:
|
|
404
|
+
"""
|
|
405
|
+
Hook to display a label in DevTools
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
value: Value to display
|
|
409
|
+
formatter: Optional function to format the value
|
|
410
|
+
"""
|
|
411
|
+
# This is a no-op in production
|
|
412
|
+
# DevTools would read this value
|
|
413
|
+
pass
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def use_id() -> str:
|
|
417
|
+
"""
|
|
418
|
+
Hook to generate unique IDs for accessibility
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
Unique ID string
|
|
422
|
+
|
|
423
|
+
Example:
|
|
424
|
+
def FormField(props):
|
|
425
|
+
id = use_id()
|
|
426
|
+
return h('div', None,
|
|
427
|
+
h('label', {'htmlFor': id}, props['label']),
|
|
428
|
+
h('input', {'id': id, 'type': 'text'})
|
|
429
|
+
)
|
|
430
|
+
"""
|
|
431
|
+
global _hook_index
|
|
432
|
+
component = _get_current_component()
|
|
433
|
+
hook_index = _hook_index
|
|
434
|
+
|
|
435
|
+
# Initialize hook if needed
|
|
436
|
+
if hook_index >= len(component._hooks):
|
|
437
|
+
component._hooks.append({
|
|
438
|
+
'type': 'id',
|
|
439
|
+
'value': f"pyreact-{uuid.uuid4().hex[:8]}"
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
hook = component._hooks[hook_index]
|
|
443
|
+
_hook_index += 1
|
|
444
|
+
|
|
445
|
+
return hook['value']
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def use_transition() -> Tuple[Callable[[Callable], None], bool]:
|
|
449
|
+
"""
|
|
450
|
+
Hook for non-blocking UI updates
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
tuple: (start_transition function, is_pending boolean)
|
|
454
|
+
|
|
455
|
+
Example:
|
|
456
|
+
def SearchResults(props):
|
|
457
|
+
is_pending, start_transition = use_transition()
|
|
458
|
+
|
|
459
|
+
def handle_change(e):
|
|
460
|
+
start_transition(lambda: set_query(e.target.value))
|
|
461
|
+
|
|
462
|
+
return h('div', None,
|
|
463
|
+
h('input', {'onChange': handle_change}),
|
|
464
|
+
is_pending and h('span', None, 'Loading...')
|
|
465
|
+
)
|
|
466
|
+
"""
|
|
467
|
+
global _hook_index
|
|
468
|
+
component = _get_current_component()
|
|
469
|
+
hook_index = _hook_index
|
|
470
|
+
|
|
471
|
+
# Initialize hook if needed
|
|
472
|
+
if hook_index >= len(component._hooks):
|
|
473
|
+
component._hooks.append({
|
|
474
|
+
'type': 'transition',
|
|
475
|
+
'is_pending': False
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
hook = component._hooks[hook_index]
|
|
479
|
+
_hook_index += 1
|
|
480
|
+
|
|
481
|
+
def start_transition(callback: Callable) -> None:
|
|
482
|
+
"""Start a non-blocking transition"""
|
|
483
|
+
hook['is_pending'] = True
|
|
484
|
+
if hasattr(component, '_schedule_update'):
|
|
485
|
+
component._schedule_update()
|
|
486
|
+
|
|
487
|
+
# Execute callback asynchronously
|
|
488
|
+
try:
|
|
489
|
+
callback()
|
|
490
|
+
finally:
|
|
491
|
+
hook['is_pending'] = False
|
|
492
|
+
if hasattr(component, '_schedule_update'):
|
|
493
|
+
component._schedule_update()
|
|
494
|
+
|
|
495
|
+
return start_transition, hook['is_pending']
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def use_deferred_value(value: Any) -> Any:
|
|
499
|
+
"""
|
|
500
|
+
Hook to defer a value for better performance
|
|
501
|
+
|
|
502
|
+
Returns a deferred version that lags behind the current value.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
value: Value to defer
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
Deferred value
|
|
509
|
+
"""
|
|
510
|
+
global _hook_index
|
|
511
|
+
component = _get_current_component()
|
|
512
|
+
hook_index = _hook_index
|
|
513
|
+
|
|
514
|
+
# Initialize hook if needed
|
|
515
|
+
if hook_index >= len(component._hooks):
|
|
516
|
+
component._hooks.append({
|
|
517
|
+
'type': 'deferred',
|
|
518
|
+
'value': value,
|
|
519
|
+
'deferred_value': value
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
hook = component._hooks[hook_index]
|
|
523
|
+
_hook_index += 1
|
|
524
|
+
|
|
525
|
+
# Update deferred value asynchronously
|
|
526
|
+
if hook['value'] != value:
|
|
527
|
+
hook['value'] = value
|
|
528
|
+
# In a real implementation, this would be scheduled
|
|
529
|
+
hook['deferred_value'] = value
|
|
530
|
+
|
|
531
|
+
return hook['deferred_value']
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
class Ref:
|
|
535
|
+
"""Reference object for use_ref"""
|
|
536
|
+
|
|
537
|
+
def __init__(self):
|
|
538
|
+
self.current: Any = None
|
|
539
|
+
|
|
540
|
+
def __repr__(self) -> str:
|
|
541
|
+
return f"Ref(current={self.current!r})"
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def _deps_changed(old_deps: Optional[List[Any]], new_deps: List[Any]) -> bool:
|
|
545
|
+
"""Check if dependencies have changed"""
|
|
546
|
+
if old_deps is None:
|
|
547
|
+
return True
|
|
548
|
+
if len(old_deps) != len(new_deps):
|
|
549
|
+
return True
|
|
550
|
+
return any(a != b for a, b in zip(old_deps, new_deps))
|