pulse-framework 0.1.75__tar.gz → 0.1.77__tar.gz

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 (151) hide show
  1. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/PKG-INFO +1 -1
  2. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/pyproject.toml +1 -1
  3. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/__init__.py +12 -0
  4. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/channel.py +6 -3
  5. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/components/for_.py +7 -8
  6. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/dom/props.py +2 -0
  7. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/__init__.py +3 -0
  8. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/__init__.pyi +11 -0
  9. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/_types.py +3 -0
  10. pulse_framework-0.1.77/src/pulse/js/animation.py +217 -0
  11. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/document.py +8 -0
  12. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/react.py +2 -2
  13. pulse_framework-0.1.77/src/pulse/refs.py +893 -0
  14. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/render_session.py +27 -0
  15. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/renderer.py +23 -1
  16. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/vdom.py +12 -1
  17. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/README.md +0 -0
  18. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/_examples.py +0 -0
  19. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/app.py +0 -0
  20. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/cli/__init__.py +0 -0
  21. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/cli/cmd.py +0 -0
  22. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/cli/dependencies.py +0 -0
  23. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/cli/folder_lock.py +0 -0
  24. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/cli/helpers.py +0 -0
  25. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/cli/logging.py +0 -0
  26. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/cli/models.py +0 -0
  27. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/cli/packages.py +0 -0
  28. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/cli/processes.py +0 -0
  29. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/cli/secrets.py +0 -0
  30. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/cli/uvicorn_log_config.py +0 -0
  31. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/code_analysis.py +0 -0
  32. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/codegen/__init__.py +0 -0
  33. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/codegen/codegen.py +0 -0
  34. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/codegen/templates/__init__.py +0 -0
  35. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/codegen/templates/layout.py +0 -0
  36. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/codegen/templates/route.py +0 -0
  37. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/codegen/templates/routes_ts.py +0 -0
  38. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/codegen/utils.py +0 -0
  39. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/component.py +0 -0
  40. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/components/__init__.py +0 -0
  41. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/components/if_.py +0 -0
  42. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/components/react_router.py +0 -0
  43. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/context.py +0 -0
  44. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/cookies.py +0 -0
  45. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/debounce.py +0 -0
  46. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/decorators.py +0 -0
  47. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/dom/__init__.py +0 -0
  48. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/dom/elements.py +0 -0
  49. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/dom/events.py +0 -0
  50. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/dom/svg.py +0 -0
  51. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/dom/tags.py +0 -0
  52. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/dom/tags.pyi +0 -0
  53. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/env.py +0 -0
  54. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/forms.py +0 -0
  55. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/helpers.py +0 -0
  56. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/hooks/__init__.py +0 -0
  57. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/hooks/core.py +0 -0
  58. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/hooks/effects.py +0 -0
  59. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/hooks/init.py +0 -0
  60. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/hooks/runtime.py +0 -0
  61. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/hooks/setup.py +0 -0
  62. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/hooks/stable.py +0 -0
  63. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/hooks/state.py +0 -0
  64. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/abort_controller.py +0 -0
  65. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/array.py +0 -0
  66. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/array_buffer.py +0 -0
  67. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/blob.py +0 -0
  68. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/console.py +0 -0
  69. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/crypto.py +0 -0
  70. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/custom_event.py +0 -0
  71. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/date.py +0 -0
  72. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/dom_parser.py +0 -0
  73. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/error.py +0 -0
  74. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/fetch_api.py +0 -0
  75. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/file.py +0 -0
  76. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/file_reader.py +0 -0
  77. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/form_data.py +0 -0
  78. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/intersection_observer.py +0 -0
  79. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/intl.py +0 -0
  80. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/json.py +0 -0
  81. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/map.py +0 -0
  82. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/math.py +0 -0
  83. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/mutation_observer.py +0 -0
  84. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/navigator.py +0 -0
  85. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/number.py +0 -0
  86. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/obj.py +0 -0
  87. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/object.py +0 -0
  88. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/performance_observer.py +0 -0
  89. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/promise.py +0 -0
  90. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/pulse.py +0 -0
  91. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/regexp.py +0 -0
  92. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/resize_observer.py +0 -0
  93. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/set.py +0 -0
  94. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/string.py +0 -0
  95. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/text_encoding.py +0 -0
  96. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/url.py +0 -0
  97. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/weakmap.py +0 -0
  98. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/weakset.py +0 -0
  99. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/js/window.py +0 -0
  100. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/messages.py +0 -0
  101. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/middleware.py +0 -0
  102. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/plugin.py +0 -0
  103. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/proxy.py +0 -0
  104. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/py.typed +0 -0
  105. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/queries/__init__.py +0 -0
  106. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/queries/client.py +0 -0
  107. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/queries/common.py +0 -0
  108. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/queries/effect.py +0 -0
  109. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/queries/infinite_query.py +0 -0
  110. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/queries/mutation.py +0 -0
  111. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/queries/protocol.py +0 -0
  112. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/queries/query.py +0 -0
  113. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/queries/store.py +0 -0
  114. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/react_component.py +0 -0
  115. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/reactive.py +0 -0
  116. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/reactive_extensions.py +0 -0
  117. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/request.py +0 -0
  118. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/requirements.py +0 -0
  119. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/routing.py +0 -0
  120. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/scheduling.py +0 -0
  121. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/serializer.py +0 -0
  122. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/state/__init__.py +0 -0
  123. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/state/property.py +0 -0
  124. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/state/query_param.py +0 -0
  125. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/state/state.py +0 -0
  126. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/test_helpers.py +0 -0
  127. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/__init__.py +0 -0
  128. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/assets.py +0 -0
  129. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/builtins.py +0 -0
  130. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/dynamic_import.py +0 -0
  131. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/emit_context.py +0 -0
  132. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/errors.py +0 -0
  133. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/function.py +0 -0
  134. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/id.py +0 -0
  135. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/imports.py +0 -0
  136. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/js_module.py +0 -0
  137. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/modules/__init__.py +0 -0
  138. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/modules/asyncio.py +0 -0
  139. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/modules/json.py +0 -0
  140. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/modules/math.py +0 -0
  141. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/modules/pulse/__init__.py +0 -0
  142. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/modules/pulse/tags.py +0 -0
  143. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/modules/typing.py +0 -0
  144. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/nodes.py +0 -0
  145. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/parse.py +0 -0
  146. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/py_module.py +0 -0
  147. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/transpiler/transpiler.py +0 -0
  148. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/types/__init__.py +0 -0
  149. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/types/event_handler.py +0 -0
  150. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/user_session.py +0 -0
  151. {pulse_framework-0.1.75 → pulse_framework-0.1.77}/src/pulse/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pulse-framework
3
- Version: 0.1.75
3
+ Version: 0.1.77
4
4
  Summary: Pulse - Full-stack framework for building real-time React applications in Python
5
5
  Requires-Dist: fastapi>=0.128.0
6
6
  Requires-Dist: uvicorn>=0.24.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pulse-framework"
3
- version = "0.1.75"
3
+ version = "0.1.77"
4
4
  description = "Pulse - Full-stack framework for building real-time React applications in Python"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1409,6 +1409,18 @@ from pulse.reactive_extensions import (
1409
1409
  from pulse.reactive_extensions import (
1410
1410
  unwrap as unwrap,
1411
1411
  )
1412
+ from pulse.refs import (
1413
+ RefHandle as RefHandle,
1414
+ )
1415
+ from pulse.refs import (
1416
+ RefNotMounted as RefNotMounted,
1417
+ )
1418
+ from pulse.refs import (
1419
+ RefTimeout as RefTimeout,
1420
+ )
1421
+ from pulse.refs import (
1422
+ ref as ref,
1423
+ )
1412
1424
 
1413
1425
  # JavaScript execution
1414
1426
  from pulse.render_session import JsExecError as JsExecError
@@ -81,7 +81,9 @@ class ChannelsManager:
81
81
  self.pending_requests = {}
82
82
 
83
83
  # ------------------------------------------------------------------
84
- def create(self, identifier: str | None = None) -> "Channel":
84
+ def create(
85
+ self, identifier: str | None = None, *, bind_route: bool = True
86
+ ) -> "Channel":
85
87
  ctx = PulseContext.get()
86
88
  render = ctx.render
87
89
  session = ctx.session
@@ -93,7 +95,7 @@ class ChannelsManager:
93
95
  raise ValueError(f"Channel id '{channel_id}' is already in use")
94
96
 
95
97
  route_path: str | None = None
96
- if ctx.route is not None:
98
+ if bind_route and ctx.route is not None:
97
99
  # unique_path() returns absolute path, use as-is for keys
98
100
  route_path = ctx.route.pulse_route.unique_path()
99
101
 
@@ -129,7 +131,8 @@ class ChannelsManager:
129
131
  if not response_to:
130
132
  return
131
133
 
132
- if error := message.get("error") is not None:
134
+ error = message.get("error")
135
+ if error is not None:
133
136
  self.resolve_pending_error(response_to, error)
134
137
  else:
135
138
  self._resolve_pending_success(response_to, message.get("payload"))
@@ -5,14 +5,13 @@ Provides a declarative way to render lists, similar to JavaScript's Array.map().
5
5
 
6
6
  from collections.abc import Callable, Iterable
7
7
  from inspect import Parameter, signature
8
- from typing import TYPE_CHECKING, Any, TypeVar, overload
8
+ from typing import Any, TypeVar, overload
9
9
 
10
- from pulse.transpiler.nodes import Call, Element, Expr, Member, transformer
11
-
12
- if TYPE_CHECKING:
13
- from pulse.transpiler.transpiler import Transpiler
10
+ from pulse.transpiler.nodes import Call, Expr, Member, Node, transformer
11
+ from pulse.transpiler.transpiler import Transpiler
14
12
 
15
13
  T = TypeVar("T")
14
+ TNode = TypeVar("TNode", bound=Node)
16
15
 
17
16
 
18
17
  @transformer("For")
@@ -24,14 +23,14 @@ def emit_for(items: Any, fn: Any, *, ctx: "Transpiler") -> Expr:
24
23
 
25
24
 
26
25
  @overload
27
- def For(items: Iterable[T], fn: Callable[[T], Element]) -> list[Element]: ...
26
+ def For(items: Iterable[T], fn: Callable[[T], TNode]) -> list[TNode]: ...
28
27
 
29
28
 
30
29
  @overload
31
- def For(items: Iterable[T], fn: Callable[[T, int], Element]) -> list[Element]: ...
30
+ def For(items: Iterable[T], fn: Callable[[T, int], TNode]) -> list[TNode]: ...
32
31
 
33
32
 
34
- def For(items: Iterable[T], fn: Callable[..., Element]) -> list[Element]:
33
+ def For(items: Iterable[T], fn: Callable[..., TNode]) -> list[TNode]:
35
34
  """Map items to elements, like JavaScript's Array.map().
36
35
 
37
36
  Iterates over `items` and calls `fn` for each one, returning a list of
@@ -68,6 +68,7 @@ from pulse.dom.events import (
68
68
  TextAreaDOMEvents,
69
69
  )
70
70
  from pulse.helpers import CSSProperties
71
+ from pulse.refs import RefHandle
71
72
  from pulse.transpiler.nodes import Expr
72
73
 
73
74
  Booleanish = Literal[True, False, "true", "false"]
@@ -82,6 +83,7 @@ class BaseHTMLProps(TypedDict, total=False):
82
83
  defaultValue: str | int | list[str]
83
84
  suppressContentEditableWarning: bool
84
85
  suppressHydrationWarning: bool
86
+ ref: RefHandle[Any]
85
87
 
86
88
  # Standard HTML Attributes
87
89
  accessKey: str
@@ -67,6 +67,7 @@ _MODULE_EXPORTS_NAMESPACE: dict[str, str] = {
67
67
  _MODULE_EXPORTS_ATTRIBUTE: dict[str, str] = {
68
68
  "AbortController": "pulse.js.abort_controller",
69
69
  "AbortSignal": "pulse.js.abort_controller",
70
+ "Animation": "pulse.js.animation",
70
71
  "Array": "pulse.js.array",
71
72
  "ArrayBuffer": "pulse.js.array_buffer",
72
73
  "BigInt64Array": "pulse.js.array_buffer",
@@ -76,6 +77,7 @@ _MODULE_EXPORTS_ATTRIBUTE: dict[str, str] = {
76
77
  "DOMParser": "pulse.js.dom_parser",
77
78
  "DataView": "pulse.js.array_buffer",
78
79
  "Date": "pulse.js.date",
80
+ "DocumentTimeline": "pulse.js.animation",
79
81
  "Error": "pulse.js.error",
80
82
  "File": "pulse.js.file",
81
83
  "FileReader": "pulse.js.file_reader",
@@ -87,6 +89,7 @@ _MODULE_EXPORTS_ATTRIBUTE: dict[str, str] = {
87
89
  "Int32Array": "pulse.js.array_buffer",
88
90
  "Int8Array": "pulse.js.array_buffer",
89
91
  "IntersectionObserver": "pulse.js.intersection_observer",
92
+ "KeyframeEffect": "pulse.js.animation",
90
93
  "Map": "pulse.js.map",
91
94
  "MutationObserver": "pulse.js.mutation_observer",
92
95
  "Object": "pulse.js.object",
@@ -67,6 +67,17 @@ from pulse.js._types import (
67
67
  )
68
68
  from pulse.js.abort_controller import AbortController as AbortController
69
69
  from pulse.js.abort_controller import AbortSignal as AbortSignal
70
+ from pulse.js.animation import Animation as Animation
71
+ from pulse.js.animation import ComputedEffectTiming as ComputedEffectTiming
72
+ from pulse.js.animation import ComputedKeyframe as ComputedKeyframe
73
+ from pulse.js.animation import DocumentTimeline as DocumentTimeline
74
+ from pulse.js.animation import DocumentTimelineOptions as DocumentTimelineOptions
75
+ from pulse.js.animation import EffectTiming as EffectTiming
76
+ from pulse.js.animation import Keyframe as Keyframe
77
+ from pulse.js.animation import KeyframeEffect as KeyframeEffect
78
+ from pulse.js.animation import KeyframeEffectOptions as KeyframeEffectOptions
79
+ from pulse.js.animation import OptionalEffectTiming as OptionalEffectTiming
80
+ from pulse.js.animation import PropertyIndexedKeyframes as PropertyIndexedKeyframes
70
81
 
71
82
  # Re-export classes with proper generic types
72
83
  from pulse.js.array import Array as Array
@@ -7,6 +7,7 @@ These are purely for static type checking - they have no runtime effect.
7
7
  from collections.abc import Awaitable as _Awaitable
8
8
  from collections.abc import Callable as _Callable
9
9
  from collections.abc import Iterator as _Iterator
10
+ from typing import Any as _Any
10
11
  from typing import Protocol as _Protocol
11
12
  from typing import TypeVar as _TypeVar
12
13
  from typing import overload as _overload
@@ -135,6 +136,8 @@ class Element(_Protocol):
135
136
  options: bool | dict[str, bool] | None = None,
136
137
  /,
137
138
  ) -> None: ...
139
+ def animate(self, keyframes: _Any, options: _Any | None = None, /) -> _Any: ...
140
+ def getAnimations(self) -> list[_Any]: ...
138
141
  def remove(self) -> None: ...
139
142
  def append(self, *nodes: "Element | str") -> None: ...
140
143
  def prepend(self, *nodes: "Element | str") -> None: ...
@@ -0,0 +1,217 @@
1
+ """
2
+ Web Animations API builtins.
3
+
4
+ Usage:
5
+
6
+ ```python
7
+ from pulse.js import Animation, KeyframeEffect, document, obj
8
+
9
+ @ps.javascript
10
+ def example(target):
11
+ effect = KeyframeEffect(
12
+ target,
13
+ [obj(transform="translateX(0px)"), obj(transform="translateX(100px)")],
14
+ obj(duration=300, easing="ease-in-out"),
15
+ )
16
+ animation = Animation(effect, document.timeline)
17
+ animation.play()
18
+ ```
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import Any as _Any
24
+ from typing import TypedDict as _TypedDict
25
+ from typing import overload as _overload
26
+
27
+ from pulse.js._types import Element as _Element
28
+ from pulse.js.promise import Promise as _Promise
29
+ from pulse.transpiler.js_module import JsModule
30
+
31
+ Keyframe = dict[str, _Any]
32
+ ComputedKeyframe = dict[str, _Any]
33
+ PropertyIndexedKeyframes = dict[str, _Any]
34
+
35
+
36
+ class EffectTiming(_TypedDict, total=False):
37
+ delay: float
38
+ direction: str
39
+ duration: float | str
40
+ easing: str
41
+ endDelay: float
42
+ fill: str
43
+ iterations: float
44
+ iterationStart: float
45
+
46
+
47
+ class OptionalEffectTiming(_TypedDict, total=False):
48
+ delay: float
49
+ direction: str
50
+ duration: float | str
51
+ easing: str
52
+ endDelay: float
53
+ fill: str
54
+ iterations: float
55
+ iterationStart: float
56
+
57
+
58
+ class KeyframeEffectOptions(EffectTiming, total=False):
59
+ composite: str
60
+ iterationComposite: str
61
+ pseudoElement: str
62
+
63
+
64
+ class ComputedEffectTiming(EffectTiming, total=False):
65
+ endTime: float
66
+ activeDuration: float
67
+ localTime: float | None
68
+ progress: float | None
69
+ currentIteration: float | None
70
+
71
+
72
+ class DocumentTimelineOptions(_TypedDict, total=False):
73
+ originTime: float
74
+
75
+
76
+ class KeyframeEffect:
77
+ """Keyframe-based animation effect."""
78
+
79
+ @_overload
80
+ def __init__(
81
+ self,
82
+ target: _Element | None,
83
+ keyframes: list[Keyframe] | PropertyIndexedKeyframes | None = None,
84
+ options: float | int | KeyframeEffectOptions | None = None,
85
+ /,
86
+ ) -> None: ...
87
+
88
+ @_overload
89
+ def __init__(self, source: "KeyframeEffect", /) -> None: ...
90
+
91
+ def __init__(
92
+ self,
93
+ target: _Element | None | KeyframeEffect,
94
+ keyframes: list[Keyframe] | PropertyIndexedKeyframes | None = None,
95
+ options: float | int | KeyframeEffectOptions | None = None,
96
+ /,
97
+ ) -> None: ...
98
+
99
+ @property
100
+ def target(self) -> _Element | None: ...
101
+
102
+ @property
103
+ def pseudoElement(self) -> str | None: ...
104
+
105
+ @property
106
+ def iterationComposite(self) -> str: ...
107
+
108
+ @property
109
+ def composite(self) -> str: ...
110
+
111
+ def getComputedTiming(self) -> ComputedEffectTiming: ...
112
+
113
+ def getKeyframes(self) -> list[ComputedKeyframe]: ...
114
+
115
+ def getTiming(self) -> EffectTiming: ...
116
+
117
+ def setKeyframes(
118
+ self,
119
+ keyframes: list[Keyframe] | PropertyIndexedKeyframes | None,
120
+ /,
121
+ ) -> None: ...
122
+
123
+ def updateTiming(self, timing: OptionalEffectTiming, /) -> None: ...
124
+
125
+
126
+ class Animation:
127
+ """Playback controller for a KeyframeEffect."""
128
+
129
+ def __init__(
130
+ self,
131
+ effect: KeyframeEffect | _Any | None = None,
132
+ timeline: DocumentTimeline | _Any | None = None,
133
+ /,
134
+ ) -> None: ...
135
+
136
+ @property
137
+ def currentTime(self) -> float | None: ...
138
+
139
+ @currentTime.setter
140
+ def currentTime(self, value: float | None) -> None: ...
141
+
142
+ @property
143
+ def effect(self) -> _Any | None: ...
144
+
145
+ @effect.setter
146
+ def effect(self, value: _Any | None) -> None: ...
147
+
148
+ @property
149
+ def finished(self) -> _Promise[_Any]: ...
150
+
151
+ @property
152
+ def id(self) -> str: ...
153
+
154
+ @id.setter
155
+ def id(self, value: str) -> None: ...
156
+
157
+ @property
158
+ def overallProgress(self) -> float: ...
159
+
160
+ @property
161
+ def pending(self) -> bool: ...
162
+
163
+ @property
164
+ def playState(self) -> str: ...
165
+
166
+ @property
167
+ def playbackRate(self) -> float: ...
168
+
169
+ @playbackRate.setter
170
+ def playbackRate(self, value: float) -> None: ...
171
+
172
+ @property
173
+ def ready(self) -> _Promise[_Any]: ...
174
+
175
+ @property
176
+ def replaceState(self) -> str: ...
177
+
178
+ @property
179
+ def startTime(self) -> float | None: ...
180
+
181
+ @startTime.setter
182
+ def startTime(self, value: float | None) -> None: ...
183
+
184
+ @property
185
+ def timeline(self) -> _Any | None: ...
186
+
187
+ @timeline.setter
188
+ def timeline(self, value: _Any | None) -> None: ...
189
+
190
+ def cancel(self) -> None: ...
191
+
192
+ def commitStyles(self) -> None: ...
193
+
194
+ def finish(self) -> None: ...
195
+
196
+ def pause(self) -> None: ...
197
+
198
+ def persist(self) -> None: ...
199
+
200
+ def play(self) -> None: ...
201
+
202
+ def reverse(self) -> None: ...
203
+
204
+ def updatePlaybackRate(self, rate: float, /) -> None: ...
205
+
206
+
207
+ class DocumentTimeline:
208
+ """Timeline associated with a document."""
209
+
210
+ def __init__(self, options: DocumentTimelineOptions | None = None, /) -> None: ...
211
+
212
+ @property
213
+ def currentTime(self) -> float | None: ...
214
+
215
+
216
+ # Self-register this module as a JS builtin (global identifiers)
217
+ JsModule.register(name=None)
@@ -15,6 +15,8 @@ from pulse.js._types import Element as _Element
15
15
  from pulse.js._types import HTMLCollection as _HTMLCollection
16
16
  from pulse.js._types import HTMLElement as _HTMLElement
17
17
  from pulse.js._types import NodeList as _NodeList
18
+ from pulse.js.animation import Animation as _Animation
19
+ from pulse.js.animation import DocumentTimeline as _DocumentTimeline
18
20
  from pulse.transpiler.js_module import JsModule
19
21
 
20
22
  # Read-only properties
@@ -28,6 +30,7 @@ cookie: str
28
30
  referrer: str
29
31
  URL: str
30
32
  domain: str
33
+ timeline: _DocumentTimeline
31
34
 
32
35
 
33
36
  # Query methods
@@ -122,6 +125,11 @@ def getSelection() -> _Any:
122
125
  ...
123
126
 
124
127
 
128
+ def getAnimations() -> list[_Animation]:
129
+ """Return animations associated with this document."""
130
+ ...
131
+
132
+
125
133
  # Node tree methods
126
134
  def importNode(node: _Element, deep: bool = False, /) -> _Element:
127
135
  """Import a node from another document."""
@@ -156,7 +156,7 @@ def useReducer(
156
156
 
157
157
 
158
158
  def useEffect(
159
- effect: _Callable[[], None | _Callable[[], None]],
159
+ effect: _Callable[[], None | _Callable[[], _Any]],
160
160
  deps: list[_Any] | None = None,
161
161
  ) -> None:
162
162
  """Accepts a function that contains imperative, possibly effectful code.
@@ -172,7 +172,7 @@ def useEffect(
172
172
 
173
173
 
174
174
  def useLayoutEffect(
175
- effect: _Callable[[], None | _Callable[[], None]],
175
+ effect: _Callable[[], None | _Callable[[], _Any]],
176
176
  deps: list[_Any] | None = None,
177
177
  ) -> None:
178
178
  """Like `useEffect`, but fires synchronously after all DOM mutations.