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.
Files changed (41) hide show
  1. pyreact/__init__.py +144 -0
  2. pyreact/cli/__init__.py +3 -0
  3. pyreact/cli/main.py +512 -0
  4. pyreact/core/__init__.py +80 -0
  5. pyreact/core/component.py +372 -0
  6. pyreact/core/context.py +173 -0
  7. pyreact/core/element.py +208 -0
  8. pyreact/core/error_boundary.py +145 -0
  9. pyreact/core/hooks.py +550 -0
  10. pyreact/core/memo.py +221 -0
  11. pyreact/core/portal.py +159 -0
  12. pyreact/core/reconciler.py +399 -0
  13. pyreact/core/refs.py +213 -0
  14. pyreact/core/renderer.py +112 -0
  15. pyreact/core/scheduler.py +304 -0
  16. pyreact/devtools/__init__.py +18 -0
  17. pyreact/devtools/debugger.py +314 -0
  18. pyreact/devtools/profiler.py +288 -0
  19. pyreact/dom/__init__.py +64 -0
  20. pyreact/dom/attributes.py +317 -0
  21. pyreact/dom/dom_operations.py +333 -0
  22. pyreact/dom/events.py +349 -0
  23. pyreact/server/__init__.py +34 -0
  24. pyreact/server/hydration.py +216 -0
  25. pyreact/server/ssr.py +344 -0
  26. pyreact/styles/__init__.py +19 -0
  27. pyreact/styles/css_module.py +231 -0
  28. pyreact/styles/styled.py +303 -0
  29. pyreact/testing/__init__.py +71 -0
  30. pyreact/testing/fire_event.py +355 -0
  31. pyreact/testing/screen.py +267 -0
  32. pyreact/testing/test_renderer.py +232 -0
  33. pyreact/utils/__init__.py +17 -0
  34. pyreact/utils/diff.py +182 -0
  35. pyreact/utils/object_pool.py +216 -0
  36. pyreact_framework-1.0.0.dist-info/METADATA +363 -0
  37. pyreact_framework-1.0.0.dist-info/RECORD +41 -0
  38. pyreact_framework-1.0.0.dist-info/WHEEL +5 -0
  39. pyreact_framework-1.0.0.dist-info/entry_points.txt +2 -0
  40. pyreact_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
  41. pyreact_framework-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,317 @@
1
+ """
2
+ Attributes Module
3
+ =================
4
+
5
+ This module handles mapping between HTML attributes and DOM properties.
6
+ """
7
+
8
+ from typing import Any, Dict, Optional
9
+
10
+
11
+ # HTML attributes that map to different DOM properties
12
+ PROPERTY_NAMES: Dict[str, str] = {
13
+ 'acceptCharset': 'accept-charset',
14
+ 'accessKey': 'accesskey',
15
+ 'allowFullScreen': 'allowfullscreen',
16
+ 'autoComplete': 'autocomplete',
17
+ 'autoFocus': 'autofocus',
18
+ 'autoPlay': 'autoplay',
19
+ 'cellPadding': 'cellpadding',
20
+ 'cellSpacing': 'cellspacing',
21
+ 'charSet': 'charset',
22
+ 'className': 'class',
23
+ 'colSpan': 'colspan',
24
+ 'contentEditable': 'contenteditable',
25
+ 'contextMenu': 'contextmenu',
26
+ 'crossOrigin': 'crossorigin',
27
+ 'dateTime': 'datetime',
28
+ 'encType': 'enctype',
29
+ 'formAction': 'formaction',
30
+ 'formEncType': 'formenctype',
31
+ 'formMethod': 'formmethod',
32
+ 'formNoValidate': 'formnovalidate',
33
+ 'formTarget': 'formtarget',
34
+ 'frameBorder': 'frameborder',
35
+ 'hrefLang': 'hreflang',
36
+ 'htmlFor': 'for',
37
+ 'httpEquiv': 'http-equiv',
38
+ 'isMap': 'ismap',
39
+ 'itemProp': 'itemprop',
40
+ 'itemScope': 'itemscope',
41
+ 'itemType': 'itemtype',
42
+ 'keyParams': 'keyparams',
43
+ 'keyType': 'keytype',
44
+ 'marginHeight': 'marginheight',
45
+ 'marginWidth': 'marginwidth',
46
+ 'maxLength': 'maxlength',
47
+ 'mediaGroup': 'mediagroup',
48
+ 'noValidate': 'novalidate',
49
+ 'playsInline': 'playsinline',
50
+ 'radioGroup': 'radiogroup',
51
+ 'readOnly': 'readonly',
52
+ 'rowSpan': 'rowspan',
53
+ 'spellCheck': 'spellcheck',
54
+ 'srcDoc': 'srcdoc',
55
+ 'srcLang': 'srclang',
56
+ 'srcSet': 'srcset',
57
+ 'tabIndex': 'tabindex',
58
+ 'useMap': 'usemap',
59
+ 'vocab': 'vocab',
60
+ }
61
+
62
+ # Reverse mapping
63
+ ATTRIBUTE_NAMES = {v: k for k, v in PROPERTY_NAMES.items()}
64
+
65
+ # Boolean attributes
66
+ BOOLEAN_ATTRIBUTES = {
67
+ 'allowfullscreen',
68
+ 'async',
69
+ 'autofocus',
70
+ 'autoplay',
71
+ 'checked',
72
+ 'controls',
73
+ 'default',
74
+ 'defer',
75
+ 'disabled',
76
+ 'formnovalidate',
77
+ 'hidden',
78
+ 'ismap',
79
+ 'itemscope',
80
+ 'loop',
81
+ 'multiple',
82
+ 'muted',
83
+ 'nomodule',
84
+ 'novalidate',
85
+ 'open',
86
+ 'playsinline',
87
+ 'readonly',
88
+ 'required',
89
+ 'reversed',
90
+ 'selected',
91
+ 'truespeed',
92
+ }
93
+
94
+ # Attributes that should be set as properties
95
+ PROPERTIES = {
96
+ 'value',
97
+ 'checked',
98
+ 'selected',
99
+ 'selectedIndex',
100
+ 'defaultValue',
101
+ 'defaultChecked',
102
+ }
103
+
104
+ # Attributes with units
105
+ UNITLESS_PROPERTIES = {
106
+ 'animationIterationCount',
107
+ 'borderImageOutset',
108
+ 'borderImageSlice',
109
+ 'borderImageWidth',
110
+ 'boxFlex',
111
+ 'boxFlexGroup',
112
+ 'boxOrdinalGroup',
113
+ 'columnCount',
114
+ 'columns',
115
+ 'flex',
116
+ 'flexGrow',
117
+ 'flexPositive',
118
+ 'flexShrink',
119
+ 'flexNegative',
120
+ 'flexOrder',
121
+ 'gridArea',
122
+ 'gridColumn',
123
+ 'gridColumnEnd',
124
+ 'gridColumnStart',
125
+ 'gridRow',
126
+ 'gridRowEnd',
127
+ 'gridRowStart',
128
+ 'lineClamp',
129
+ 'lineHeight',
130
+ 'opacity',
131
+ 'order',
132
+ 'orphans',
133
+ 'tabSize',
134
+ 'widows',
135
+ 'zIndex',
136
+ 'zoom',
137
+ 'fillOpacity',
138
+ 'floodOpacity',
139
+ 'stopOpacity',
140
+ 'strokeDasharray',
141
+ 'strokeDashoffset',
142
+ 'strokeMiterlimit',
143
+ 'strokeOpacity',
144
+ 'strokeWidth',
145
+ }
146
+
147
+
148
+ def is_custom_attribute(name: str) -> bool:
149
+ """
150
+ Check if an attribute is custom (data-*, aria-*)
151
+
152
+ Args:
153
+ name: Attribute name
154
+
155
+ Returns:
156
+ bool: True if custom attribute
157
+ """
158
+ return name.startswith('data-') or name.startswith('aria-')
159
+
160
+
161
+ def should_set_attribute(name: str, value: Any) -> bool:
162
+ """
163
+ Check if attribute should be set
164
+
165
+ Args:
166
+ name: Attribute name
167
+ value: Attribute value
168
+
169
+ Returns:
170
+ bool: True if should set
171
+ """
172
+ # Skip null/undefined
173
+ if value is None:
174
+ return False
175
+
176
+ # Skip event handlers (handled separately)
177
+ if name.startswith('on'):
178
+ return False
179
+
180
+ # Skip style (handled separately)
181
+ if name == 'style':
182
+ return False
183
+
184
+ # Skip ref and key
185
+ if name in ('ref', 'key'):
186
+ return False
187
+
188
+ return True
189
+
190
+
191
+ def get_attribute_name(property_name: str) -> str:
192
+ """
193
+ Get HTML attribute name from DOM property name
194
+
195
+ Args:
196
+ property_name: DOM property name
197
+
198
+ Returns:
199
+ str: HTML attribute name
200
+ """
201
+ return PROPERTY_NAMES.get(property_name, property_name.lower())
202
+
203
+
204
+ def get_property_name(attribute_name: str) -> str:
205
+ """
206
+ Get DOM property name from HTML attribute name
207
+
208
+ Args:
209
+ attribute_name: HTML attribute name
210
+
211
+ Returns:
212
+ str: DOM property name
213
+ """
214
+ return ATTRIBUTE_NAMES.get(attribute_name, attribute_name)
215
+
216
+
217
+ def is_boolean_attribute(name: str) -> bool:
218
+ """
219
+ Check if attribute is boolean
220
+
221
+ Args:
222
+ name: Attribute name
223
+
224
+ Returns:
225
+ bool: True if boolean attribute
226
+ """
227
+ return name.lower() in BOOLEAN_ATTRIBUTES
228
+
229
+
230
+ def is_property(name: str) -> bool:
231
+ """
232
+ Check if attribute should be set as a property
233
+
234
+ Args:
235
+ name: Attribute name
236
+
237
+ Returns:
238
+ bool: True if should be property
239
+ """
240
+ return name in PROPERTIES
241
+
242
+
243
+ def get_style_value(name: str, value: Any) -> str:
244
+ """
245
+ Convert style value to string
246
+
247
+ Args:
248
+ name: CSS property name
249
+ value: CSS value
250
+
251
+ Returns:
252
+ str: Style string
253
+ """
254
+ if value is None or value == '':
255
+ return ''
256
+
257
+ if isinstance(value, number):
258
+ if name not in UNITLESS_PROPERTIES:
259
+ return f"{value}px"
260
+ return str(value)
261
+
262
+ return str(value)
263
+
264
+
265
+ def escape_html_value(value: str) -> str:
266
+ """
267
+ Escape HTML special characters
268
+
269
+ Args:
270
+ value: Value to escape
271
+
272
+ Returns:
273
+ str: Escaped value
274
+ """
275
+ return (
276
+ str(value)
277
+ .replace('&', '&')
278
+ .replace('<', '&lt;')
279
+ .replace('>', '&gt;')
280
+ .replace('"', '&quot;')
281
+ .replace("'", '&#x27;')
282
+ )
283
+
284
+
285
+ def render_attributes(props: Dict[str, Any]) -> str:
286
+ """
287
+ Render attributes as HTML string
288
+
289
+ Args:
290
+ props: Props dictionary
291
+
292
+ Returns:
293
+ str: HTML attribute string
294
+ """
295
+ result = []
296
+
297
+ for name, value in props.items():
298
+ if not should_set_attribute(name, value):
299
+ continue
300
+
301
+ attr_name = get_attribute_name(name)
302
+
303
+ if is_boolean_attribute(attr_name):
304
+ if value:
305
+ result.append(attr_name)
306
+ else:
307
+ escaped = escape_html_value(value)
308
+ result.append(f'{attr_name}="{escaped}"')
309
+
310
+ return ' '.join(result)
311
+
312
+
313
+ # Number type check (for Python 2/3 compatibility)
314
+ try:
315
+ number = (int, float)
316
+ except NameError:
317
+ number = (int, float)
@@ -0,0 +1,333 @@
1
+ """
2
+ DOM Operations Module
3
+ =====================
4
+
5
+ This module provides low-level DOM manipulation functions.
6
+ These are platform-agnostic and can be adapted for different environments.
7
+ """
8
+
9
+ from typing import Any, Callable, Dict, Optional
10
+
11
+
12
+ # Platform-specific DOM implementation
13
+ # In a real implementation, this would use a JavaScript bridge
14
+
15
+ class DOMNode:
16
+ """Base class for DOM nodes"""
17
+
18
+ def __init__(self, node_type: str):
19
+ self.node_type = node_type
20
+ self.parent_node: Optional['DOMNode'] = None
21
+ self.child_nodes: list = []
22
+
23
+ def append_child(self, child: 'DOMNode') -> 'DOMNode':
24
+ """Append a child node"""
25
+ child.parent_node = self
26
+ self.child_nodes.append(child)
27
+ return child
28
+
29
+ def remove_child(self, child: 'DOMNode') -> 'DOMNode':
30
+ """Remove a child node"""
31
+ if child in self.child_nodes:
32
+ child.parent_node = None
33
+ self.child_nodes.remove(child)
34
+ return child
35
+
36
+ def insert_before(self, new_node: 'DOMNode', ref_node: Optional['DOMNode']) -> 'DOMNode':
37
+ """Insert a node before a reference node"""
38
+ new_node.parent_node = self
39
+ if ref_node is None:
40
+ self.child_nodes.append(new_node)
41
+ else:
42
+ index = self.child_nodes.index(ref_node)
43
+ self.child_nodes.insert(index, new_node)
44
+ return new_node
45
+
46
+
47
+ class Element(DOMNode):
48
+ """DOM Element"""
49
+
50
+ def __init__(self, tag_name: str):
51
+ super().__init__('element')
52
+ self.tag_name = tag_name.lower()
53
+ self.attributes: Dict[str, str] = {}
54
+ self.style: Dict[str, str] = {}
55
+ self._event_listeners: Dict[str, list] = {}
56
+ self._text_content: str = ''
57
+
58
+ def set_attribute(self, name: str, value: str) -> None:
59
+ """Set an attribute"""
60
+ self.attributes[name] = value
61
+
62
+ def get_attribute(self, name: str) -> Optional[str]:
63
+ """Get an attribute"""
64
+ return self.attributes.get(name)
65
+
66
+ def remove_attribute(self, name: str) -> None:
67
+ """Remove an attribute"""
68
+ if name in self.attributes:
69
+ del self.attributes[name]
70
+
71
+ def set_style(self, name: str, value: str) -> None:
72
+ """Set a style property"""
73
+ self.style[name] = value
74
+
75
+ def add_event_listener(self, event_type: str, listener: Callable) -> None:
76
+ """Add an event listener"""
77
+ if event_type not in self._event_listeners:
78
+ self._event_listeners[event_type] = []
79
+ self._event_listeners[event_type].append(listener)
80
+
81
+ def remove_event_listener(self, event_type: str, listener: Callable) -> None:
82
+ """Remove an event listener"""
83
+ if event_type in self._event_listeners:
84
+ if listener in self._event_listeners[event_type]:
85
+ self._event_listeners[event_type].remove(listener)
86
+
87
+ def set_inner_html(self, html: str) -> None:
88
+ """Set inner HTML"""
89
+ self._text_content = html
90
+ self.child_nodes.clear()
91
+
92
+ def insert_child(self, child: 'DOMNode', index: int) -> None:
93
+ """Insert a child at a specific index"""
94
+ child.parent_node = self
95
+ self.child_nodes.insert(index, child)
96
+
97
+ def remove_child_at(self, index: int) -> None:
98
+ """Remove a child at a specific index"""
99
+ if 0 <= index < len(self.child_nodes):
100
+ child = self.child_nodes.pop(index)
101
+ child.parent_node = None
102
+
103
+ def replace_child_at(self, new_child: 'DOMNode', index: int) -> None:
104
+ """Replace a child at a specific index"""
105
+ if 0 <= index < len(self.child_nodes):
106
+ old_child = self.child_nodes[index]
107
+ old_child.parent_node = None
108
+ new_child.parent_node = self
109
+ self.child_nodes[index] = new_child
110
+
111
+ def move_child(self, old_index: int, new_index: int) -> None:
112
+ """Move a child from old_index to new_index"""
113
+ if 0 <= old_index < len(self.child_nodes) and 0 <= new_index < len(self.child_nodes):
114
+ child = self.child_nodes.pop(old_index)
115
+ self.child_nodes.insert(new_index, child)
116
+
117
+ @property
118
+ def first_child(self) -> Optional['DOMNode']:
119
+ """Get first child"""
120
+ return self.child_nodes[0] if self.child_nodes else None
121
+
122
+ @property
123
+ def inner_html(self) -> str:
124
+ """Get inner HTML"""
125
+ return self._text_content
126
+
127
+ @inner_html.setter
128
+ def inner_html(self, value: str):
129
+ """Set inner HTML"""
130
+ self._text_content = value
131
+ self.child_nodes.clear()
132
+
133
+ def get_element_by_id(self, id: str) -> Optional['Element']:
134
+ """Find element by ID"""
135
+ if self.attributes.get('id') == id:
136
+ return self
137
+ for child in self.child_nodes:
138
+ if isinstance(child, Element):
139
+ result = child.get_element_by_id(id)
140
+ if result:
141
+ return result
142
+ return None
143
+
144
+ def query_selector(self, selector: str) -> Optional['Element']:
145
+ """Query selector (simplified)"""
146
+ # Simplified implementation
147
+ for child in self.child_nodes:
148
+ if isinstance(child, Element):
149
+ if selector.startswith('#'):
150
+ if child.attributes.get('id') == selector[1:]:
151
+ return child
152
+ elif selector.startswith('.'):
153
+ class_name = selector[1:]
154
+ if class_name in child.attributes.get('class', '').split():
155
+ return child
156
+ elif child.tag_name == selector.lower():
157
+ return child
158
+ return None
159
+
160
+ def query_selector_all(self, selector: str) -> list:
161
+ """Query all matching elements"""
162
+ results = []
163
+ for child in self.child_nodes:
164
+ if isinstance(child, Element):
165
+ if selector.startswith('#'):
166
+ if child.attributes.get('id') == selector[1:]:
167
+ results.append(child)
168
+ elif selector.startswith('.'):
169
+ class_name = selector[1:]
170
+ if class_name in child.attributes.get('class', '').split():
171
+ results.append(child)
172
+ elif child.tag_name == selector.lower():
173
+ results.append(child)
174
+ results.extend(child.query_selector_all(selector))
175
+ return results
176
+
177
+ def focus(self) -> None:
178
+ """Focus the element"""
179
+ pass
180
+
181
+ def blur(self) -> None:
182
+ """Blur the element"""
183
+ pass
184
+
185
+ def click(self) -> None:
186
+ """Click the element"""
187
+ pass
188
+
189
+ def scroll_into_view(self) -> None:
190
+ """Scroll element into view"""
191
+ pass
192
+
193
+ def get_bounding_client_rect(self) -> Dict[str, float]:
194
+ """Get element dimensions"""
195
+ return {
196
+ 'x': 0, 'y': 0,
197
+ 'width': 0, 'height': 0,
198
+ 'top': 0, 'right': 0,
199
+ 'bottom': 0, 'left': 0
200
+ }
201
+
202
+ def __repr__(self) -> str:
203
+ return f"<{self.tag_name}>"
204
+
205
+
206
+ class TextNode(DOMNode):
207
+ """Text node"""
208
+
209
+ def __init__(self, text: str):
210
+ super().__init__('text')
211
+ self.text_content = text
212
+
213
+ def __repr__(self) -> str:
214
+ return f"#text: {self.text_content[:20]}..."
215
+
216
+
217
+ class CommentNode(DOMNode):
218
+ """Comment node"""
219
+
220
+ def __init__(self, text: str):
221
+ super().__init__('comment')
222
+ self.text_content = text
223
+
224
+
225
+ class Document(Element):
226
+ """Document object"""
227
+
228
+ def __init__(self):
229
+ super().__init__('html')
230
+ self.node_type = 'document'
231
+ self.body = Element('body')
232
+ self.head = Element('head')
233
+ self.append_child(self.head)
234
+ self.append_child(self.body)
235
+
236
+ def create_element(self, tag_name: str) -> Element:
237
+ """Create an element"""
238
+ return Element(tag_name)
239
+
240
+ def create_text_node(self, text: str) -> TextNode:
241
+ """Create a text node"""
242
+ return TextNode(text)
243
+
244
+ def create_comment(self, text: str) -> CommentNode:
245
+ """Create a comment node"""
246
+ return CommentNode(text)
247
+
248
+ def get_element_by_id(self, id: str) -> Optional[Element]:
249
+ """Find element by ID"""
250
+ return self.body.get_element_by_id(id)
251
+
252
+ def query_selector(self, selector: str) -> Optional[Element]:
253
+ """Query selector"""
254
+ return self.body.query_selector(selector)
255
+
256
+ def query_selector_all(self, selector: str) -> list:
257
+ """Query all matching elements"""
258
+ return self.body.query_selector_all(selector)
259
+
260
+
261
+ # Global document instance
262
+ document = Document()
263
+
264
+
265
+ def create_element(tag_name: str) -> Element:
266
+ """Create a DOM element"""
267
+ return document.create_element(tag_name)
268
+
269
+
270
+ def create_text_node(text: str) -> TextNode:
271
+ """Create a text node"""
272
+ return document.create_text_node(text)
273
+
274
+
275
+ def append_child(parent: DOMNode, child: DOMNode) -> DOMNode:
276
+ """Append a child to a parent"""
277
+ return parent.append_child(child)
278
+
279
+
280
+ def remove_child(parent: DOMNode, child: DOMNode) -> DOMNode:
281
+ """Remove a child from a parent"""
282
+ return parent.remove_child(child)
283
+
284
+
285
+ def insert_before(parent: DOMNode, new_node: DOMNode, ref_node: Optional[DOMNode]) -> DOMNode:
286
+ """Insert a node before a reference node"""
287
+ return parent.insert_before(new_node, ref_node)
288
+
289
+
290
+ def set_attribute(element: Element, name: str, value: str) -> None:
291
+ """Set an attribute on an element"""
292
+ element.attributes[name] = value
293
+
294
+
295
+ def remove_attribute(element: Element, name: str) -> None:
296
+ """Remove an attribute from an element"""
297
+ if name in element.attributes:
298
+ del element.attributes[name]
299
+
300
+
301
+ def set_style(element: Element, styles: Dict[str, str]) -> None:
302
+ """Set styles on an element"""
303
+ element.style.update(styles)
304
+
305
+
306
+ def add_event_listener(
307
+ element: Element,
308
+ event_type: str,
309
+ listener: Callable,
310
+ options: Optional[Dict] = None
311
+ ) -> None:
312
+ """Add an event listener to an element"""
313
+ if event_type not in element._event_listeners:
314
+ element._event_listeners[event_type] = []
315
+ element._event_listeners[event_type].append(listener)
316
+
317
+
318
+ def remove_event_listener(
319
+ element: Element,
320
+ event_type: str,
321
+ listener: Callable
322
+ ) -> None:
323
+ """Remove an event listener from an element"""
324
+ if event_type in element._event_listeners:
325
+ if listener in element._event_listeners[event_type]:
326
+ element._event_listeners[event_type].remove(listener)
327
+
328
+
329
+ def dispatch_event(element: Element, event_type: str, event_data: Optional[Dict] = None) -> None:
330
+ """Dispatch an event on an element"""
331
+ if event_type in element._event_listeners:
332
+ for listener in element._event_listeners[event_type]:
333
+ listener(event_data or {})