reflex 0.7.2a2__py3-none-any.whl → 0.7.3__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 reflex might be problematic. Click here for more details.

Files changed (45) hide show
  1. reflex/.templates/jinja/web/pages/custom_component.js.jinja2 +6 -3
  2. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +1 -1
  3. reflex/.templates/web/components/shiki/code.js +26 -21
  4. reflex/.templates/web/postcss.config.js +1 -1
  5. reflex/.templates/web/utils/client_side_routing.js +18 -16
  6. reflex/.templates/web/utils/helpers/dataeditor.js +1 -1
  7. reflex/.templates/web/utils/helpers/range.js +30 -30
  8. reflex/.templates/web/utils/state.js +44 -22
  9. reflex/app_mixins/middleware.py +9 -10
  10. reflex/compiler/compiler.py +7 -0
  11. reflex/components/core/banner.py +1 -1
  12. reflex/components/core/foreach.py +3 -2
  13. reflex/components/datadisplay/logo.py +1 -1
  14. reflex/components/el/__init__.pyi +22 -0
  15. reflex/components/el/elements/__init__.py +20 -1
  16. reflex/components/el/elements/__init__.pyi +42 -1
  17. reflex/components/el/elements/media.py +11 -0
  18. reflex/components/el/elements/media.pyi +11 -0
  19. reflex/components/lucide/icon.py +8 -6
  20. reflex/components/lucide/icon.pyi +0 -1
  21. reflex/components/radix/themes/components/slider.py +2 -1
  22. reflex/config.py +13 -7
  23. reflex/custom_components/custom_components.py +23 -25
  24. reflex/event.py +7 -2
  25. reflex/istate/data.py +59 -7
  26. reflex/reflex.py +75 -32
  27. reflex/state.py +3 -3
  28. reflex/style.py +3 -3
  29. reflex/testing.py +4 -10
  30. reflex/utils/exceptions.py +31 -1
  31. reflex/utils/exec.py +4 -8
  32. reflex/utils/export.py +2 -2
  33. reflex/utils/prerequisites.py +3 -45
  34. reflex/utils/pyi_generator.py +2 -2
  35. reflex/utils/redir.py +3 -12
  36. reflex/vars/base.py +26 -3
  37. reflex/vars/number.py +22 -21
  38. reflex/vars/object.py +29 -0
  39. reflex/vars/sequence.py +37 -0
  40. {reflex-0.7.2a2.dist-info → reflex-0.7.3.dist-info}/METADATA +52 -59
  41. {reflex-0.7.2a2.dist-info → reflex-0.7.3.dist-info}/RECORD +64 -64
  42. {reflex-0.7.2a2.dist-info → reflex-0.7.3.dist-info}/WHEEL +1 -1
  43. reflex-0.7.3.dist-info/entry_points.txt +2 -0
  44. reflex-0.7.2a2.dist-info/entry_points.txt +0 -3
  45. {reflex-0.7.2a2.dist-info → reflex-0.7.3.dist-info/licenses}/LICENSE +0 -0
@@ -1,11 +1,14 @@
1
1
  {% extends "web/pages/base_page.js.jinja2" %}
2
2
  {% from "web/pages/macros.js.jinja2" import renderHooks %}
3
- {% block export %}
4
- {% for component in components %}
5
3
 
6
- {% for custom_code in component.custom_code %}
4
+ {% block declaration %}
5
+ {% for custom_code in custom_codes %}
7
6
  {{custom_code}}
8
7
  {% endfor %}
8
+ {% endblock %}
9
+
10
+ {% block export %}
11
+ {% for component in components %}
9
12
 
10
13
  export const {{component.name}} = memo(({ {{-component.props|join(", ")-}} }) => {
11
14
  {{ renderHooks(component.hooks) }}
@@ -11,7 +11,7 @@ export default function RadixThemesColorModeProvider({ children }) {
11
11
  const { theme, resolvedTheme, setTheme } = useTheme();
12
12
  const [rawColorMode, setRawColorMode] = useState(defaultColorMode);
13
13
  const [resolvedColorMode, setResolvedColorMode] = useState(
14
- defaultColorMode === "dark" ? "dark" : "light"
14
+ defaultColorMode === "dark" ? "dark" : "light",
15
15
  );
16
16
 
17
17
  useEffect(() => {
@@ -1,5 +1,5 @@
1
- import { useEffect, useState } from "react"
2
- import { codeToHtml} from "shiki"
1
+ import { useEffect, useState } from "react";
2
+ import { codeToHtml } from "shiki";
3
3
 
4
4
  /**
5
5
  * Code component that uses Shiki to convert code to HTML and render it.
@@ -12,23 +12,28 @@ import { codeToHtml} from "shiki"
12
12
  * @param divProps - Additional properties to be passed to the div element.
13
13
  * @returns The rendered code block.
14
14
  */
15
- export function Code ({code, theme, language, transformers, decorations, ...divProps}) {
16
- const [codeResult, setCodeResult] = useState("")
17
- useEffect(() => {
18
- async function fetchCode() {
19
- const result = await codeToHtml(code, {
20
- lang: language,
21
- theme,
22
- transformers,
23
- decorations
24
- });
25
- setCodeResult(result);
26
- }
27
- fetchCode();
28
- }, [code, language, theme, transformers, decorations]
29
-
30
- )
31
- return (
32
- <div dangerouslySetInnerHTML={{__html: codeResult}} {...divProps} ></div>
33
- )
15
+ export function Code({
16
+ code,
17
+ theme,
18
+ language,
19
+ transformers,
20
+ decorations,
21
+ ...divProps
22
+ }) {
23
+ const [codeResult, setCodeResult] = useState("");
24
+ useEffect(() => {
25
+ async function fetchCode() {
26
+ const result = await codeToHtml(code, {
27
+ lang: language,
28
+ theme,
29
+ transformers,
30
+ decorations,
31
+ });
32
+ setCodeResult(result);
33
+ }
34
+ fetchCode();
35
+ }, [code, language, theme, transformers, decorations]);
36
+ return (
37
+ <div dangerouslySetInnerHTML={{ __html: codeResult }} {...divProps}></div>
38
+ );
34
39
  }
@@ -4,4 +4,4 @@ module.exports = {
4
4
  tailwindcss: {},
5
5
  autoprefixer: {},
6
6
  },
7
- }
7
+ };
@@ -10,32 +10,34 @@ import { useRouter } from "next/router";
10
10
  * @returns {boolean} routeNotFound - true if the current route is an actual 404
11
11
  */
12
12
  export const useClientSideRouting = () => {
13
- const [routeNotFound, setRouteNotFound] = useState(false)
14
- const didRedirect = useRef(false)
15
- const router = useRouter()
13
+ const [routeNotFound, setRouteNotFound] = useState(false);
14
+ const didRedirect = useRef(false);
15
+ const router = useRouter();
16
16
  useEffect(() => {
17
17
  if (
18
18
  router.isReady &&
19
- !didRedirect.current // have not tried redirecting yet
19
+ !didRedirect.current // have not tried redirecting yet
20
20
  ) {
21
- didRedirect.current = true // never redirect twice to avoid "Hard Navigate" error
21
+ didRedirect.current = true; // never redirect twice to avoid "Hard Navigate" error
22
22
  // attempt to redirect to the route in the browser address bar once
23
- router.replace({
23
+ router
24
+ .replace({
24
25
  pathname: window.location.pathname,
25
26
  query: window.location.search.slice(1),
26
- }).then(()=>{
27
+ })
28
+ .then(() => {
27
29
  // Check if the current route is /404
28
- if (router.pathname === '/404') {
29
- setRouteNotFound(true); // Mark as an actual 404
30
- }
31
- })
32
- .catch((e) => {
33
- setRouteNotFound(true) // navigation failed, so this is a real 404
34
- })
30
+ if (router.pathname === "/404") {
31
+ setRouteNotFound(true); // Mark as an actual 404
32
+ }
33
+ })
34
+ .catch((e) => {
35
+ setRouteNotFound(true); // navigation failed, so this is a real 404
36
+ });
35
37
  }
36
38
  }, [router.isReady]);
37
39
 
38
40
  // Return the reactive bool, to avoid flashing 404 page until we know for sure
39
41
  // the route is not found.
40
- return routeNotFound
41
- }
42
+ return routeNotFound;
43
+ };
@@ -48,7 +48,7 @@ export function formatCell(value, column) {
48
48
  };
49
49
  default:
50
50
  console.log(
51
- "Warning: column.type is undefined for column.title=" + column.title
51
+ "Warning: column.type is undefined for column.title=" + column.title,
52
52
  );
53
53
  return {
54
54
  kind: GridCellKind.Text,
@@ -1,43 +1,43 @@
1
1
  /**
2
2
  * Simulate the python range() builtin function.
3
3
  * inspired by https://dev.to/guyariely/using-python-range-in-javascript-337p
4
- *
4
+ *
5
5
  * If needed outside of an iterator context, use `Array.from(range(10))` or
6
6
  * spread syntax `[...range(10)]` to get an array.
7
- *
7
+ *
8
8
  * @param {number} start: the start or end of the range.
9
9
  * @param {number} stop: the end of the range.
10
10
  * @param {number} step: the step of the range.
11
11
  * @returns {object} an object with a Symbol.iterator method over the range
12
12
  */
13
13
  export default function range(start, stop, step) {
14
- return {
15
- [Symbol.iterator]() {
16
- if (stop === undefined) {
17
- stop = start;
18
- start = 0;
19
- }
20
- if (step === undefined) {
21
- step = 1;
22
- }
23
-
24
- let i = start - step;
25
-
26
- return {
27
- next() {
28
- i += step;
29
- if ((step > 0 && i < stop) || (step < 0 && i > stop)) {
30
- return {
31
- value: i,
32
- done: false,
33
- };
34
- }
14
+ return {
15
+ [Symbol.iterator]() {
16
+ if (stop === undefined) {
17
+ stop = start;
18
+ start = 0;
19
+ }
20
+ if (step === undefined) {
21
+ step = 1;
22
+ }
23
+
24
+ let i = start - step;
25
+
26
+ return {
27
+ next() {
28
+ i += step;
29
+ if ((step > 0 && i < stop) || (step < 0 && i > stop)) {
35
30
  return {
36
- value: undefined,
37
- done: true,
31
+ value: i,
32
+ done: false,
38
33
  };
39
- },
40
- };
41
- },
42
- };
43
- }
34
+ }
35
+ return {
36
+ value: undefined,
37
+ done: true,
38
+ };
39
+ },
40
+ };
41
+ },
42
+ };
43
+ }
@@ -227,8 +227,8 @@ export const applyEvent = async (event, socket) => {
227
227
  a.href = eval?.(
228
228
  event.payload.url.replace(
229
229
  "getBackendURL(env.UPLOAD)",
230
- `"${getBackendURL(env.UPLOAD)}"`
231
- )
230
+ `"${getBackendURL(env.UPLOAD)}"`,
231
+ ),
232
232
  );
233
233
  }
234
234
  a.download = event.payload.filename;
@@ -341,7 +341,7 @@ export const applyRestEvent = async (event, socket) => {
341
341
  event.payload.files,
342
342
  event.payload.upload_id,
343
343
  event.payload.on_upload_progress,
344
- socket
344
+ socket,
345
345
  );
346
346
  return false;
347
347
  }
@@ -352,8 +352,18 @@ export const applyRestEvent = async (event, socket) => {
352
352
  * Queue events to be processed and trigger processing of queue.
353
353
  * @param events Array of events to queue.
354
354
  * @param socket The socket object to send the event on.
355
+ * @param prepend Whether to place the events at the beginning of the queue.
355
356
  */
356
- export const queueEvents = async (events, socket) => {
357
+ export const queueEvents = async (events, socket, prepend) => {
358
+ if (prepend) {
359
+ // Drain the existing queue and place it after the given events.
360
+ events = [
361
+ ...events,
362
+ ...Array.from({ length: event_queue.length }).map(() =>
363
+ event_queue.shift(),
364
+ ),
365
+ ];
366
+ }
357
367
  event_queue.push(...events);
358
368
  await processEvent(socket.current);
359
369
  };
@@ -408,7 +418,7 @@ export const connect = async (
408
418
  dispatch,
409
419
  transports,
410
420
  setConnectErrors,
411
- client_storage = {}
421
+ client_storage = {},
412
422
  ) => {
413
423
  // Get backend URL object from the endpoint.
414
424
  const endpoint = getBackendURL(EVENTURL);
@@ -441,6 +451,13 @@ export const connect = async (
441
451
  }
442
452
  }
443
453
 
454
+ const disconnectTrigger = (event) => {
455
+ if (socket.current?.connected) {
456
+ console.log("Disconnect websocket on unload");
457
+ socket.current.disconnect();
458
+ }
459
+ };
460
+
444
461
  const pagehideHandler = (event) => {
445
462
  if (event.persisted && socket.current?.connected) {
446
463
  console.log("Disconnect backend before bfcache on navigation");
@@ -452,6 +469,8 @@ export const connect = async (
452
469
  socket.current.on("connect", () => {
453
470
  setConnectErrors([]);
454
471
  window.addEventListener("pagehide", pagehideHandler);
472
+ window.addEventListener("beforeunload", disconnectTrigger);
473
+ window.addEventListener("unload", disconnectTrigger);
455
474
  });
456
475
 
457
476
  socket.current.on("connect_error", (error) => {
@@ -461,6 +480,8 @@ export const connect = async (
461
480
  // When the socket disconnects reset the event_processing flag
462
481
  socket.current.on("disconnect", () => {
463
482
  event_processing = false;
483
+ window.removeEventListener("unload", disconnectTrigger);
484
+ window.removeEventListener("beforeunload", disconnectTrigger);
464
485
  window.removeEventListener("pagehide", pagehideHandler);
465
486
  });
466
487
 
@@ -477,7 +498,7 @@ export const connect = async (
477
498
  });
478
499
  socket.current.on("reload", async (event) => {
479
500
  event_processing = false;
480
- queueEvents([...initialEvents(), event], socket);
501
+ queueEvents([...initialEvents(), event], socket, true);
481
502
  });
482
503
 
483
504
  document.addEventListener("visibilitychange", checkVisibility);
@@ -499,7 +520,7 @@ export const uploadFiles = async (
499
520
  files,
500
521
  upload_id,
501
522
  on_upload_progress,
502
- socket
523
+ socket,
503
524
  ) => {
504
525
  // return if there's no file to upload
505
526
  if (files === undefined || files.length === 0) {
@@ -604,7 +625,7 @@ export const Event = (
604
625
  name,
605
626
  payload = {},
606
627
  event_actions = {},
607
- handler = null
628
+ handler = null,
608
629
  ) => {
609
630
  return { name, payload, handler, event_actions };
610
631
  };
@@ -631,7 +652,7 @@ export const hydrateClientStorage = (client_storage) => {
631
652
  for (const state_key in client_storage.local_storage) {
632
653
  const options = client_storage.local_storage[state_key];
633
654
  const local_storage_value = localStorage.getItem(
634
- options.name || state_key
655
+ options.name || state_key,
635
656
  );
636
657
  if (local_storage_value !== null) {
637
658
  client_storage_values[state_key] = local_storage_value;
@@ -642,7 +663,7 @@ export const hydrateClientStorage = (client_storage) => {
642
663
  for (const state_key in client_storage.session_storage) {
643
664
  const session_options = client_storage.session_storage[state_key];
644
665
  const session_storage_value = sessionStorage.getItem(
645
- session_options.name || state_key
666
+ session_options.name || state_key,
646
667
  );
647
668
  if (session_storage_value != null) {
648
669
  client_storage_values[state_key] = session_storage_value;
@@ -667,7 +688,7 @@ export const hydrateClientStorage = (client_storage) => {
667
688
  const applyClientStorageDelta = (client_storage, delta) => {
668
689
  // find the main state and check for is_hydrated
669
690
  const unqualified_states = Object.keys(delta).filter(
670
- (key) => key.split(".").length === 1
691
+ (key) => key.split(".").length === 1,
671
692
  );
672
693
  if (unqualified_states.length === 1) {
673
694
  const main_state = delta[unqualified_states[0]];
@@ -701,7 +722,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
701
722
  const session_options = client_storage.session_storage[state_key];
702
723
  sessionStorage.setItem(
703
724
  session_options.name || state_key,
704
- delta[substate][key]
725
+ delta[substate][key],
705
726
  );
706
727
  }
707
728
  }
@@ -721,7 +742,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
721
742
  export const useEventLoop = (
722
743
  dispatch,
723
744
  initial_events = () => [],
724
- client_storage = {}
745
+ client_storage = {},
725
746
  ) => {
726
747
  const socket = useRef(null);
727
748
  const router = useRouter();
@@ -735,7 +756,7 @@ export const useEventLoop = (
735
756
 
736
757
  event_actions = events.reduce(
737
758
  (acc, e) => ({ ...acc, ...e.event_actions }),
738
- event_actions ?? {}
759
+ event_actions ?? {},
739
760
  );
740
761
 
741
762
  const _e = args.filter((o) => o?.preventDefault !== undefined)[0];
@@ -763,7 +784,7 @@ export const useEventLoop = (
763
784
  debounce(
764
785
  combined_name,
765
786
  () => queueEvents(events, socket),
766
- event_actions.debounce
787
+ event_actions.debounce,
767
788
  );
768
789
  } else {
769
790
  queueEvents(events, socket);
@@ -773,16 +794,17 @@ export const useEventLoop = (
773
794
  const sentHydrate = useRef(false); // Avoid double-hydrate due to React strict-mode
774
795
  useEffect(() => {
775
796
  if (router.isReady && !sentHydrate.current) {
776
- const events = initial_events();
777
- addEvents(
778
- events.map((e) => ({
797
+ queueEvents(
798
+ initial_events().map((e) => ({
779
799
  ...e,
780
800
  router_data: (({ pathname, query, asPath }) => ({
781
801
  pathname,
782
802
  query,
783
803
  asPath,
784
804
  }))(router),
785
- }))
805
+ })),
806
+ socket,
807
+ true,
786
808
  );
787
809
  sentHydrate.current = true;
788
810
  }
@@ -828,7 +850,7 @@ export const useEventLoop = (
828
850
  dispatch,
829
851
  ["websocket"],
830
852
  setConnectErrors,
831
- client_storage
853
+ client_storage,
832
854
  );
833
855
  }
834
856
  }
@@ -876,7 +898,7 @@ export const useEventLoop = (
876
898
  vars[storage_to_state_map[e.key]] = e.newValue;
877
899
  const event = Event(
878
900
  `${state_name}.reflex___state____update_vars_internal_state.update_vars_internal`,
879
- { vars: vars }
901
+ { vars: vars },
880
902
  );
881
903
  addEvents([event], e);
882
904
  }
@@ -969,7 +991,7 @@ export const getRefValues = (refs) => {
969
991
  return refs.map((ref) =>
970
992
  ref.current
971
993
  ? ref.current.value || ref.current.getAttribute("aria-valuenow")
972
- : null
994
+ : null,
973
995
  );
974
996
  };
975
997
 
@@ -16,11 +16,11 @@ from .mixin import AppMixin
16
16
  class MiddlewareMixin(AppMixin):
17
17
  """Middleware Mixin that allow to add middleware to the app."""
18
18
 
19
- # Middleware to add to the app. Users should use `add_middleware`. PRIVATE.
20
- middleware: list[Middleware] = dataclasses.field(default_factory=list)
19
+ # Middleware to add to the app. Users should use `add_middleware`.
20
+ _middlewares: list[Middleware] = dataclasses.field(default_factory=list)
21
21
 
22
22
  def _init_mixin(self):
23
- self.middleware.append(HydrateMiddleware())
23
+ self._middlewares.append(HydrateMiddleware())
24
24
 
25
25
  def add_middleware(self, middleware: Middleware, index: int | None = None):
26
26
  """Add middleware to the app.
@@ -30,9 +30,9 @@ class MiddlewareMixin(AppMixin):
30
30
  index: The index to add the middleware at.
31
31
  """
32
32
  if index is None:
33
- self.middleware.append(middleware)
33
+ self._middlewares.append(middleware)
34
34
  else:
35
- self.middleware.insert(index, middleware)
35
+ self._middlewares.insert(index, middleware)
36
36
 
37
37
  async def _preprocess(self, state: BaseState, event: Event) -> StateUpdate | None:
38
38
  """Preprocess the event.
@@ -50,7 +50,7 @@ class MiddlewareMixin(AppMixin):
50
50
  Returns:
51
51
  An optional state to return.
52
52
  """
53
- for middleware in self.middleware:
53
+ for middleware in self._middlewares:
54
54
  if asyncio.iscoroutinefunction(middleware.preprocess):
55
55
  out = await middleware.preprocess(app=self, state=state, event=event) # pyright: ignore [reportArgumentType]
56
56
  else:
@@ -74,7 +74,8 @@ class MiddlewareMixin(AppMixin):
74
74
  Returns:
75
75
  The state update to return.
76
76
  """
77
- for middleware in self.middleware:
77
+ out = update
78
+ for middleware in self._middlewares:
78
79
  if asyncio.iscoroutinefunction(middleware.postprocess):
79
80
  out = await middleware.postprocess(
80
81
  app=self, # pyright: ignore [reportArgumentType]
@@ -89,6 +90,4 @@ class MiddlewareMixin(AppMixin):
89
90
  event=event,
90
91
  update=update,
91
92
  )
92
- if out is not None:
93
- return out # pyright: ignore [reportReturnType]
94
- return update
93
+ return out # pyright: ignore[reportReturnType]
@@ -247,12 +247,19 @@ def _compile_components(
247
247
  for comp_import in comp_render["dynamic_imports"]
248
248
  }
249
249
 
250
+ custom_codes = {
251
+ comp_custom_code: None
252
+ for comp_render in component_renders
253
+ for comp_custom_code in comp_render.get("custom_code", [])
254
+ }
255
+
250
256
  # Compile the components page.
251
257
  return (
252
258
  templates.COMPONENTS.render(
253
259
  imports=utils.compile_imports(imports),
254
260
  components=component_renders,
255
261
  dynamic_imports=dynamic_imports,
262
+ custom_codes=custom_codes,
256
263
  ),
257
264
  imports,
258
265
  )
@@ -382,7 +382,7 @@ class BackendDisabled(Div):
382
382
  ),
383
383
  rx.hstack(
384
384
  rx.el.svg(
385
- rx.el.svg.path(
385
+ rx.el.path(
386
386
  d="M6.90816 1.34341C7.61776 1.10786 8.38256 1.10786 9.09216 1.34341C9.7989 1.57799 10.3538 2.13435 10.9112 2.91605C11.4668 3.69515 12.0807 4.78145 12.872 6.18175L12.9031 6.23672C13.6946 7.63721 14.3085 8.72348 14.6911 9.60441C15.0755 10.4896 15.267 11.2539 15.1142 11.9881C14.9604 12.7275 14.5811 13.3997 14.0287 13.9079C13.4776 14.4147 12.7273 14.6286 11.7826 14.7313C10.8432 14.8334 9.6143 14.8334 8.0327 14.8334H7.9677C6.38604 14.8334 5.15719 14.8334 4.21778 14.7313C3.27301 14.6286 2.52269 14.4147 1.97164 13.9079C1.41924 13.3997 1.03995 12.7275 0.88613 11.9881C0.733363 11.2539 0.92483 10.4896 1.30926 9.60441C1.69184 8.72348 2.30573 7.63721 3.09722 6.23671L3.12828 6.18175C3.91964 4.78146 4.53355 3.69515 5.08914 2.91605C5.64658 2.13435 6.20146 1.57799 6.90816 1.34341ZM7.3335 11.3334C7.3335 10.9652 7.63063 10.6667 7.99716 10.6667H8.00316C8.3697 10.6667 8.66683 10.9652 8.66683 11.3334C8.66683 11.7016 8.3697 12.0001 8.00316 12.0001H7.99716C7.63063 12.0001 7.3335 11.7016 7.3335 11.3334ZM7.3335 8.66675C7.3335 9.03495 7.63196 9.33341 8.00016 9.33341C8.36836 9.33341 8.66683 9.03495 8.66683 8.66675V6.00008C8.66683 5.63189 8.36836 5.33341 8.00016 5.33341C7.63196 5.33341 7.3335 5.63189 7.3335 6.00008V8.66675Z",
387
387
  fill_rule="evenodd",
388
388
  clip_rule="evenodd",
@@ -100,8 +100,9 @@ class Foreach(Component):
100
100
  component.children = [component._render().render_component()]
101
101
  except UntypedVarError as e:
102
102
  raise UntypedVarError(
103
- f"Could not foreach over var `{iterable!s}` without a type annotation. "
104
- "See https://reflex.dev/docs/library/dynamic-rendering/foreach/"
103
+ iterable,
104
+ "foreach",
105
+ "https://reflex.dev/docs/library/dynamic-rendering/foreach/",
105
106
  ) from e
106
107
  return component
107
108
 
@@ -18,7 +18,7 @@ def svg_logo(
18
18
  """
19
19
 
20
20
  def logo_path(d: str):
21
- return rx.el.svg.path(d=d)
21
+ return rx.el.path(d=d)
22
22
 
23
23
  paths = [
24
24
  "M0 11.5999V0.399902H8.96V4.8799H6.72V2.6399H2.24V4.8799H6.72V7.1199H2.24V11.5999H0ZM6.72 11.5999V7.1199H8.96V11.5999H6.72Z",
@@ -90,29 +90,51 @@ from .elements.inline import u as u
90
90
  from .elements.inline import wbr as wbr
91
91
  from .elements.media import Area as Area
92
92
  from .elements.media import Audio as Audio
93
+ from .elements.media import Circle as Circle
94
+ from .elements.media import Defs as Defs
95
+ from .elements.media import Ellipse as Ellipse
93
96
  from .elements.media import Embed as Embed
94
97
  from .elements.media import Iframe as Iframe
95
98
  from .elements.media import Img as Img
99
+ from .elements.media import Line as Line
100
+ from .elements.media import LinearGradient as LinearGradient
96
101
  from .elements.media import Map as Map
97
102
  from .elements.media import Object as Object
103
+ from .elements.media import Path as Path
98
104
  from .elements.media import Picture as Picture
105
+ from .elements.media import Polygon as Polygon
99
106
  from .elements.media import Portal as Portal
107
+ from .elements.media import RadialGradient as RadialGradient
108
+ from .elements.media import Rect as Rect
100
109
  from .elements.media import Source as Source
110
+ from .elements.media import Stop as Stop
101
111
  from .elements.media import Svg as Svg
112
+ from .elements.media import Text as Text
102
113
  from .elements.media import Track as Track
103
114
  from .elements.media import Video as Video
104
115
  from .elements.media import area as area
105
116
  from .elements.media import audio as audio
117
+ from .elements.media import circle as circle
118
+ from .elements.media import defs as defs
119
+ from .elements.media import ellipse as ellipse
106
120
  from .elements.media import embed as embed
107
121
  from .elements.media import iframe as iframe
108
122
  from .elements.media import image as image
109
123
  from .elements.media import img as img
124
+ from .elements.media import line as line
125
+ from .elements.media import linear_gradient as linear_gradient
110
126
  from .elements.media import map as map
111
127
  from .elements.media import object as object
128
+ from .elements.media import path as path
112
129
  from .elements.media import picture as picture
130
+ from .elements.media import polygon as polygon
113
131
  from .elements.media import portal as portal
132
+ from .elements.media import radial_gradient as radial_gradient
133
+ from .elements.media import rect as rect
114
134
  from .elements.media import source as source
135
+ from .elements.media import stop as stop
115
136
  from .elements.media import svg as svg
137
+ from .elements.media import text as text
116
138
  from .elements.media import track as track
117
139
  from .elements.media import video as video
118
140
  from .elements.metadata import Base as Base
@@ -66,6 +66,17 @@ _MAPPING = {
66
66
  "portal",
67
67
  "source",
68
68
  "svg",
69
+ "text",
70
+ "line",
71
+ "circle",
72
+ "ellipse",
73
+ "rect",
74
+ "polygon",
75
+ "path",
76
+ "stop",
77
+ "linear_gradient",
78
+ "radial_gradient",
79
+ "defs",
69
80
  ],
70
81
  "metadata": [
71
82
  "base",
@@ -128,7 +139,15 @@ _MAPPING = {
128
139
 
129
140
  EXCLUDE = ["del_", "Del", "image"]
130
141
  for v in _MAPPING.values():
131
- v.extend([mod.capitalize() for mod in v if mod not in EXCLUDE])
142
+ from reflex.utils.format import to_camel_case
143
+
144
+ v.extend(
145
+ [
146
+ to_camel_case(mod)[0].upper() + to_camel_case(mod)[1:]
147
+ for mod in v
148
+ if mod not in EXCLUDE
149
+ ]
150
+ )
132
151
 
133
152
  _SUBMOD_ATTRS: dict[str, list[str]] = _MAPPING
134
153