reflex 0.8.5a1__py3-none-any.whl → 0.8.6a0__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.
- reflex/.templates/{web/vite.config.js → jinja/web/vite.config.js.jinja2} +11 -0
- reflex/.templates/web/utils/state.js +5 -0
- reflex/__init__.py +1 -0
- reflex/__init__.pyi +2 -0
- reflex/app.py +65 -13
- reflex/compiler/templates.py +3 -0
- reflex/compiler/utils.py +6 -12
- reflex/components/core/auto_scroll.py +14 -13
- reflex/components/datadisplay/dataeditor.py +3 -0
- reflex/components/datadisplay/dataeditor.pyi +2 -0
- reflex/components/el/__init__.pyi +4 -0
- reflex/components/el/elements/__init__.py +1 -0
- reflex/components/el/elements/__init__.pyi +5 -0
- reflex/components/el/elements/media.py +32 -0
- reflex/components/el/elements/media.pyi +261 -0
- reflex/components/lucide/icon.py +4 -1
- reflex/components/lucide/icon.pyi +4 -1
- reflex/components/sonner/toast.py +1 -1
- reflex/config.py +15 -0
- reflex/constants/base.py +7 -58
- reflex/environment.py +0 -10
- reflex/state.py +27 -5
- reflex/testing.py +11 -0
- reflex/utils/decorator.py +1 -0
- reflex/utils/monitoring.py +180 -0
- reflex/utils/prerequisites.py +17 -0
- reflex/utils/token_manager.py +215 -0
- {reflex-0.8.5a1.dist-info → reflex-0.8.6a0.dist-info}/METADATA +5 -2
- {reflex-0.8.5a1.dist-info → reflex-0.8.6a0.dist-info}/RECORD +32 -30
- {reflex-0.8.5a1.dist-info → reflex-0.8.6a0.dist-info}/WHEEL +0 -0
- {reflex-0.8.5a1.dist-info → reflex-0.8.6a0.dist-info}/entry_points.txt +0 -0
- {reflex-0.8.5a1.dist-info → reflex-0.8.6a0.dist-info}/licenses/LICENSE +0 -0
|
@@ -25,6 +25,7 @@ function alwaysUseReactDomServerNode() {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export default defineConfig((config) => ({
|
|
28
|
+
base: "{{base}}",
|
|
28
29
|
plugins: [
|
|
29
30
|
alwaysUseReactDomServerNode(),
|
|
30
31
|
reactRouter(),
|
|
@@ -33,6 +34,16 @@ export default defineConfig((config) => ({
|
|
|
33
34
|
build: {
|
|
34
35
|
rollupOptions: {
|
|
35
36
|
jsx: {},
|
|
37
|
+
output: {
|
|
38
|
+
advancedChunks: {
|
|
39
|
+
groups: [
|
|
40
|
+
{
|
|
41
|
+
test: /env.json/,
|
|
42
|
+
name: "reflex-env",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
36
47
|
},
|
|
37
48
|
},
|
|
38
49
|
server: {
|
|
@@ -530,6 +530,7 @@ export const connect = async (
|
|
|
530
530
|
transports: transports,
|
|
531
531
|
protocols: [reflexEnvironment.version],
|
|
532
532
|
autoUnref: false,
|
|
533
|
+
query: { token: getToken() },
|
|
533
534
|
});
|
|
534
535
|
// Ensure undefined fields in events are sent as null instead of removed
|
|
535
536
|
socket.current.io.encoder.replacer = (k, v) => (v === undefined ? null : v);
|
|
@@ -601,6 +602,10 @@ export const connect = async (
|
|
|
601
602
|
event_processing = false;
|
|
602
603
|
queueEvents([...initialEvents(), event], socket, true, navigate, params);
|
|
603
604
|
});
|
|
605
|
+
socket.current.on("new_token", async (new_token) => {
|
|
606
|
+
token = new_token;
|
|
607
|
+
window.sessionStorage.setItem(TOKEN_KEY, new_token);
|
|
608
|
+
});
|
|
604
609
|
|
|
605
610
|
document.addEventListener("visibilitychange", checkVisibility);
|
|
606
611
|
};
|
reflex/__init__.py
CHANGED
|
@@ -285,6 +285,7 @@ _MAPPING: dict = {
|
|
|
285
285
|
"data_editor_theme",
|
|
286
286
|
],
|
|
287
287
|
"components.sonner.toast": ["toast"],
|
|
288
|
+
"components.props": ["PropsBase"],
|
|
288
289
|
"components.datadisplay.logo": ["logo"],
|
|
289
290
|
"components.gridjs": ["data_table"],
|
|
290
291
|
"components.moment": ["MomentDelta", "moment"],
|
reflex/__init__.pyi
CHANGED
|
@@ -62,6 +62,7 @@ from .components.lucide import icon
|
|
|
62
62
|
from .components.markdown import markdown
|
|
63
63
|
from .components.moment import MomentDelta, moment
|
|
64
64
|
from .components.plotly import plotly
|
|
65
|
+
from .components.props import PropsBase
|
|
65
66
|
from .components.radix.primitives.accordion import accordion
|
|
66
67
|
from .components.radix.primitives.drawer import drawer
|
|
67
68
|
from .components.radix.primitives.form import form
|
|
@@ -193,6 +194,7 @@ __all__ = [
|
|
|
193
194
|
"Model",
|
|
194
195
|
"MomentDelta",
|
|
195
196
|
"NoSSRComponent",
|
|
197
|
+
"PropsBase",
|
|
196
198
|
"Script",
|
|
197
199
|
"SessionStorage",
|
|
198
200
|
"State",
|
reflex/app.py
CHANGED
|
@@ -13,6 +13,7 @@ import io
|
|
|
13
13
|
import json
|
|
14
14
|
import sys
|
|
15
15
|
import traceback
|
|
16
|
+
import urllib.parse
|
|
16
17
|
from collections.abc import (
|
|
17
18
|
AsyncGenerator,
|
|
18
19
|
AsyncIterator,
|
|
@@ -114,6 +115,7 @@ from reflex.utils import (
|
|
|
114
115
|
)
|
|
115
116
|
from reflex.utils.exec import get_compile_context, is_prod_mode, is_testing_env
|
|
116
117
|
from reflex.utils.imports import ImportVar
|
|
118
|
+
from reflex.utils.token_manager import TokenManager
|
|
117
119
|
from reflex.utils.types import ASGIApp, Message, Receive, Scope, Send
|
|
118
120
|
|
|
119
121
|
if TYPE_CHECKING:
|
|
@@ -1958,12 +1960,6 @@ class EventNamespace(AsyncNamespace):
|
|
|
1958
1960
|
# The application object.
|
|
1959
1961
|
app: App
|
|
1960
1962
|
|
|
1961
|
-
# Keep a mapping between socket ID and client token.
|
|
1962
|
-
token_to_sid: dict[str, str]
|
|
1963
|
-
|
|
1964
|
-
# Keep a mapping between client token and socket ID.
|
|
1965
|
-
sid_to_token: dict[str, str]
|
|
1966
|
-
|
|
1967
1963
|
def __init__(self, namespace: str, app: App):
|
|
1968
1964
|
"""Initialize the event namespace.
|
|
1969
1965
|
|
|
@@ -1972,17 +1968,45 @@ class EventNamespace(AsyncNamespace):
|
|
|
1972
1968
|
app: The application object.
|
|
1973
1969
|
"""
|
|
1974
1970
|
super().__init__(namespace)
|
|
1975
|
-
self.token_to_sid = {}
|
|
1976
|
-
self.sid_to_token = {}
|
|
1977
1971
|
self.app = app
|
|
1978
1972
|
|
|
1979
|
-
|
|
1973
|
+
# Use TokenManager for distributed duplicate tab prevention
|
|
1974
|
+
self._token_manager = TokenManager.create()
|
|
1975
|
+
|
|
1976
|
+
@property
|
|
1977
|
+
def token_to_sid(self) -> dict[str, str]:
|
|
1978
|
+
"""Get token to SID mapping for backward compatibility.
|
|
1979
|
+
|
|
1980
|
+
Returns:
|
|
1981
|
+
The token to SID mapping dict.
|
|
1982
|
+
"""
|
|
1983
|
+
# For backward compatibility, expose the underlying dict
|
|
1984
|
+
return self._token_manager.token_to_sid
|
|
1985
|
+
|
|
1986
|
+
@property
|
|
1987
|
+
def sid_to_token(self) -> dict[str, str]:
|
|
1988
|
+
"""Get SID to token mapping for backward compatibility.
|
|
1989
|
+
|
|
1990
|
+
Returns:
|
|
1991
|
+
The SID to token mapping dict.
|
|
1992
|
+
"""
|
|
1993
|
+
# For backward compatibility, expose the underlying dict
|
|
1994
|
+
return self._token_manager.sid_to_token
|
|
1995
|
+
|
|
1996
|
+
async def on_connect(self, sid: str, environ: dict):
|
|
1980
1997
|
"""Event for when the websocket is connected.
|
|
1981
1998
|
|
|
1982
1999
|
Args:
|
|
1983
2000
|
sid: The Socket.IO session id.
|
|
1984
2001
|
environ: The request information, including HTTP headers.
|
|
1985
2002
|
"""
|
|
2003
|
+
query_params = urllib.parse.parse_qs(environ.get("QUERY_STRING", ""))
|
|
2004
|
+
token_list = query_params.get("token", [])
|
|
2005
|
+
if token_list:
|
|
2006
|
+
await self.link_token_to_sid(sid, token_list[0])
|
|
2007
|
+
else:
|
|
2008
|
+
console.warn(f"No token provided in connection for session {sid}")
|
|
2009
|
+
|
|
1986
2010
|
subprotocol = environ.get("HTTP_SEC_WEBSOCKET_PROTOCOL")
|
|
1987
2011
|
if subprotocol and subprotocol != constants.Reflex.VERSION:
|
|
1988
2012
|
console.warn(
|
|
@@ -1995,9 +2019,18 @@ class EventNamespace(AsyncNamespace):
|
|
|
1995
2019
|
Args:
|
|
1996
2020
|
sid: The Socket.IO session id.
|
|
1997
2021
|
"""
|
|
1998
|
-
|
|
2022
|
+
# Get token before cleaning up
|
|
2023
|
+
disconnect_token = self.sid_to_token.get(sid)
|
|
1999
2024
|
if disconnect_token:
|
|
2000
|
-
|
|
2025
|
+
# Use async cleanup through token manager
|
|
2026
|
+
task = asyncio.create_task(
|
|
2027
|
+
self._token_manager.disconnect_token(disconnect_token, sid)
|
|
2028
|
+
)
|
|
2029
|
+
# Don't await to avoid blocking disconnect, but handle potential errors
|
|
2030
|
+
task.add_done_callback(
|
|
2031
|
+
lambda t: t.exception()
|
|
2032
|
+
and console.error(f"Token cleanup error: {t.exception()}")
|
|
2033
|
+
)
|
|
2001
2034
|
|
|
2002
2035
|
async def emit_update(self, update: StateUpdate, sid: str) -> None:
|
|
2003
2036
|
"""Emit an update to the client.
|
|
@@ -2055,8 +2088,13 @@ class EventNamespace(AsyncNamespace):
|
|
|
2055
2088
|
msg = f"Failed to deserialize event data: {fields}."
|
|
2056
2089
|
raise exceptions.EventDeserializationError(msg) from ex
|
|
2057
2090
|
|
|
2058
|
-
|
|
2059
|
-
self.sid_to_token
|
|
2091
|
+
# Correct the token if it doesn't match what we expect for this SID
|
|
2092
|
+
expected_token = self.sid_to_token.get(sid)
|
|
2093
|
+
if expected_token and event.token != expected_token:
|
|
2094
|
+
# Create new event with corrected token since Event is frozen
|
|
2095
|
+
from dataclasses import replace
|
|
2096
|
+
|
|
2097
|
+
event = replace(event, token=expected_token)
|
|
2060
2098
|
|
|
2061
2099
|
# Get the event environment.
|
|
2062
2100
|
if self.app.sio is None:
|
|
@@ -2106,3 +2144,17 @@ class EventNamespace(AsyncNamespace):
|
|
|
2106
2144
|
"""
|
|
2107
2145
|
# Emit the test event.
|
|
2108
2146
|
await self.emit(str(constants.SocketEvent.PING), "pong", to=sid)
|
|
2147
|
+
|
|
2148
|
+
async def link_token_to_sid(self, sid: str, token: str):
|
|
2149
|
+
"""Link a token to a session id.
|
|
2150
|
+
|
|
2151
|
+
Args:
|
|
2152
|
+
sid: The Socket.IO session id.
|
|
2153
|
+
token: The client token.
|
|
2154
|
+
"""
|
|
2155
|
+
# Use TokenManager for duplicate detection and Redis support
|
|
2156
|
+
new_token = await self._token_manager.link_token_to_sid(token, sid)
|
|
2157
|
+
|
|
2158
|
+
if new_token:
|
|
2159
|
+
# Duplicate detected, emit new token to client
|
|
2160
|
+
await self.emit("new_token", new_token, to=sid)
|
reflex/compiler/templates.py
CHANGED
|
@@ -149,6 +149,9 @@ STYLE = get_template("web/styles/styles.css.jinja2")
|
|
|
149
149
|
# Code that generate the package json file
|
|
150
150
|
PACKAGE_JSON = get_template("web/package.json.jinja2")
|
|
151
151
|
|
|
152
|
+
# Code that generate the vite.config.js file
|
|
153
|
+
VITE_CONFIG = get_template("web/vite.config.js.jinja2")
|
|
154
|
+
|
|
152
155
|
# Template containing some macros used in the web pages.
|
|
153
156
|
MACROS = get_template("web/pages/macros.js.jinja2")
|
|
154
157
|
|
reflex/compiler/utils.py
CHANGED
|
@@ -23,8 +23,7 @@ from reflex.constants.state import FIELD_MARKER
|
|
|
23
23
|
from reflex.istate.storage import Cookie, LocalStorage, SessionStorage
|
|
24
24
|
from reflex.state import BaseState, _resolve_delta
|
|
25
25
|
from reflex.style import Style
|
|
26
|
-
from reflex.utils import
|
|
27
|
-
from reflex.utils.exec import is_in_app_harness
|
|
26
|
+
from reflex.utils import format, imports, path_ops
|
|
28
27
|
from reflex.utils.imports import ImportVar, ParsedImportDict
|
|
29
28
|
from reflex.utils.prerequisites import get_web_dir
|
|
30
29
|
from reflex.vars.base import Field, Var
|
|
@@ -201,16 +200,11 @@ def compile_state(state: type[BaseState]) -> dict:
|
|
|
201
200
|
except RuntimeError:
|
|
202
201
|
pass
|
|
203
202
|
else:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
).result()
|
|
210
|
-
console.warn(
|
|
211
|
-
f"Had to get initial state in a thread 🤮 {resolved_initial_state}",
|
|
212
|
-
)
|
|
213
|
-
return _sorted_keys(resolved_initial_state)
|
|
203
|
+
with concurrent.futures.ThreadPoolExecutor() as pool:
|
|
204
|
+
resolved_initial_state = pool.submit(
|
|
205
|
+
asyncio.run, _resolve_delta(initial_state)
|
|
206
|
+
).result()
|
|
207
|
+
return _sorted_keys(resolved_initial_state)
|
|
214
208
|
|
|
215
209
|
# Normally the compile runs before any event loop starts, we asyncio.run is available for calling.
|
|
216
210
|
return _sorted_keys(asyncio.run(_resolve_delta(initial_state)))
|
|
@@ -52,11 +52,12 @@ class AutoScroll(Div):
|
|
|
52
52
|
The hooks required for the component.
|
|
53
53
|
"""
|
|
54
54
|
ref_name = self.get_ref()
|
|
55
|
+
unique_id = ref_name
|
|
55
56
|
return [
|
|
56
|
-
"const
|
|
57
|
-
"const
|
|
57
|
+
f"const wasNearBottom_{unique_id} = useRef(false);",
|
|
58
|
+
f"const hadScrollbar_{unique_id} = useRef(false);",
|
|
58
59
|
f"""
|
|
59
|
-
const
|
|
60
|
+
const checkIfNearBottom_{unique_id} = () => {{
|
|
60
61
|
if (!{ref_name}.current) return;
|
|
61
62
|
|
|
62
63
|
const container = {ref_name}.current;
|
|
@@ -64,14 +65,14 @@ const checkIfNearBottom = () => {{
|
|
|
64
65
|
|
|
65
66
|
const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
wasNearBottom_{unique_id}.current = distanceFromBottom <= nearBottomThreshold;
|
|
68
69
|
|
|
69
70
|
// Track if container had a scrollbar
|
|
70
|
-
|
|
71
|
+
hadScrollbar_{unique_id}.current = container.scrollHeight > container.clientHeight;
|
|
71
72
|
}};
|
|
72
73
|
""",
|
|
73
74
|
f"""
|
|
74
|
-
const
|
|
75
|
+
const scrollToBottomIfNeeded_{unique_id} = () => {{
|
|
75
76
|
if (!{ref_name}.current) return;
|
|
76
77
|
|
|
77
78
|
const container = {ref_name}.current;
|
|
@@ -80,12 +81,12 @@ const scrollToBottomIfNeeded = () => {{
|
|
|
80
81
|
// Scroll if:
|
|
81
82
|
// 1. User was near bottom, OR
|
|
82
83
|
// 2. Container didn't have scrollbar before but does now
|
|
83
|
-
if (
|
|
84
|
+
if (wasNearBottom_{unique_id}.current || (!hadScrollbar_{unique_id}.current && hasScrollbarNow)) {{
|
|
84
85
|
container.scrollTop = container.scrollHeight;
|
|
85
86
|
}}
|
|
86
87
|
|
|
87
88
|
// Update scrollbar state for next check
|
|
88
|
-
|
|
89
|
+
hadScrollbar_{unique_id}.current = hasScrollbarNow;
|
|
89
90
|
}};
|
|
90
91
|
""",
|
|
91
92
|
f"""
|
|
@@ -93,24 +94,24 @@ useEffect(() => {{
|
|
|
93
94
|
const container = {ref_name}.current;
|
|
94
95
|
if (!container) return;
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
scrollToBottomIfNeeded_{unique_id}();
|
|
97
98
|
|
|
98
99
|
// Create ResizeObserver to detect height changes
|
|
99
100
|
const resizeObserver = new ResizeObserver(() => {{
|
|
100
|
-
|
|
101
|
+
scrollToBottomIfNeeded_{unique_id}();
|
|
101
102
|
}});
|
|
102
103
|
|
|
103
104
|
// Track scroll position before height changes
|
|
104
|
-
container.addEventListener('scroll',
|
|
105
|
+
container.addEventListener('scroll', checkIfNearBottom_{unique_id});
|
|
105
106
|
|
|
106
107
|
// Initial check
|
|
107
|
-
|
|
108
|
+
checkIfNearBottom_{unique_id}();
|
|
108
109
|
|
|
109
110
|
// Observe container for size changes
|
|
110
111
|
resizeObserver.observe(container);
|
|
111
112
|
|
|
112
113
|
return () => {{
|
|
113
|
-
container.removeEventListener('scroll',
|
|
114
|
+
container.removeEventListener('scroll', checkIfNearBottom_{unique_id});
|
|
114
115
|
resizeObserver.disconnect();
|
|
115
116
|
}};
|
|
116
117
|
}});
|
|
@@ -197,6 +197,9 @@ class DataEditor(NoSSRComponent):
|
|
|
197
197
|
# Enables or disables the overlay shadow when scrolling vertically.
|
|
198
198
|
fixed_shadow_y: Var[bool]
|
|
199
199
|
|
|
200
|
+
# Controls the presence of the fill indicator
|
|
201
|
+
fill_handle: Var[bool]
|
|
202
|
+
|
|
200
203
|
# The number of columns which should remain in place when scrolling horizontally. Doesn't include rowMarkers.
|
|
201
204
|
freeze_columns: Var[int]
|
|
202
205
|
|
|
@@ -141,6 +141,7 @@ class DataEditor(NoSSRComponent):
|
|
|
141
141
|
draw_focus_ring: Var[bool] | bool | None = None,
|
|
142
142
|
fixed_shadow_x: Var[bool] | bool | None = None,
|
|
143
143
|
fixed_shadow_y: Var[bool] | bool | None = None,
|
|
144
|
+
fill_handle: Var[bool] | bool | None = None,
|
|
144
145
|
freeze_columns: Var[int] | int | None = None,
|
|
145
146
|
group_header_height: Var[int] | int | None = None,
|
|
146
147
|
header_height: Var[int] | int | None = None,
|
|
@@ -245,6 +246,7 @@ class DataEditor(NoSSRComponent):
|
|
|
245
246
|
draw_focus_ring: Controls the drawing of the focus ring.
|
|
246
247
|
fixed_shadow_x: Enables or disables the overlay shadow when scrolling horizontally.
|
|
247
248
|
fixed_shadow_y: Enables or disables the overlay shadow when scrolling vertically.
|
|
249
|
+
fill_handle: Controls the presence of the fill indicator
|
|
248
250
|
freeze_columns: The number of columns which should remain in place when scrolling horizontally. Doesn't include rowMarkers.
|
|
249
251
|
group_header_height: Controls the header of the group header row.
|
|
250
252
|
header_height: Controls the height of the header row.
|
|
@@ -106,6 +106,7 @@ from .elements.media import (
|
|
|
106
106
|
Line,
|
|
107
107
|
LinearGradient,
|
|
108
108
|
Map,
|
|
109
|
+
Marker,
|
|
109
110
|
Object,
|
|
110
111
|
Path,
|
|
111
112
|
Picture,
|
|
@@ -132,6 +133,7 @@ from .elements.media import (
|
|
|
132
133
|
line,
|
|
133
134
|
linear_gradient,
|
|
134
135
|
map,
|
|
136
|
+
marker,
|
|
135
137
|
object,
|
|
136
138
|
path,
|
|
137
139
|
picture,
|
|
@@ -332,6 +334,7 @@ __all__ = [
|
|
|
332
334
|
"Main",
|
|
333
335
|
"Map",
|
|
334
336
|
"Mark",
|
|
337
|
+
"Marker",
|
|
335
338
|
"Math",
|
|
336
339
|
"Meta",
|
|
337
340
|
"Meter",
|
|
@@ -457,6 +460,7 @@ __all__ = [
|
|
|
457
460
|
"main",
|
|
458
461
|
"map",
|
|
459
462
|
"mark",
|
|
463
|
+
"marker",
|
|
460
464
|
"math",
|
|
461
465
|
"meta",
|
|
462
466
|
"meter",
|
|
@@ -104,6 +104,7 @@ from .media import (
|
|
|
104
104
|
Line,
|
|
105
105
|
LinearGradient,
|
|
106
106
|
Map,
|
|
107
|
+
Marker,
|
|
107
108
|
Object,
|
|
108
109
|
Path,
|
|
109
110
|
Picture,
|
|
@@ -130,6 +131,7 @@ from .media import (
|
|
|
130
131
|
line,
|
|
131
132
|
linear_gradient,
|
|
132
133
|
map,
|
|
134
|
+
marker,
|
|
133
135
|
object,
|
|
134
136
|
path,
|
|
135
137
|
picture,
|
|
@@ -336,6 +338,7 @@ _MAPPING = {
|
|
|
336
338
|
"linear_gradient",
|
|
337
339
|
"radial_gradient",
|
|
338
340
|
"defs",
|
|
341
|
+
"marker",
|
|
339
342
|
],
|
|
340
343
|
"metadata": ["base", "head", "link", "meta", "title", "style"],
|
|
341
344
|
"other": ["details", "dialog", "summary", "slot", "template", "math", "html"],
|
|
@@ -469,6 +472,7 @@ __all__ = [
|
|
|
469
472
|
"Main",
|
|
470
473
|
"Map",
|
|
471
474
|
"Mark",
|
|
475
|
+
"Marker",
|
|
472
476
|
"Math",
|
|
473
477
|
"Meta",
|
|
474
478
|
"Meter",
|
|
@@ -593,6 +597,7 @@ __all__ = [
|
|
|
593
597
|
"main",
|
|
594
598
|
"map",
|
|
595
599
|
"mark",
|
|
600
|
+
"marker",
|
|
596
601
|
"math",
|
|
597
602
|
"meta",
|
|
598
603
|
"meter",
|
|
@@ -484,6 +484,36 @@ class Path(BaseHTML):
|
|
|
484
484
|
d: Var[str | int | float]
|
|
485
485
|
|
|
486
486
|
|
|
487
|
+
class Marker(BaseHTML):
|
|
488
|
+
"""Display the marker element."""
|
|
489
|
+
|
|
490
|
+
tag = "marker"
|
|
491
|
+
|
|
492
|
+
# The height of the marker viewport.
|
|
493
|
+
marker_height: Var[str | int | float]
|
|
494
|
+
|
|
495
|
+
# The width of the marker viewport.
|
|
496
|
+
marker_width: Var[str | int | float]
|
|
497
|
+
|
|
498
|
+
# The coordinate system for the marker attributes.
|
|
499
|
+
marker_units: Var[str]
|
|
500
|
+
|
|
501
|
+
# The orientation of the marker relative to the shape it is attached to.
|
|
502
|
+
orient: Var[str | int | float]
|
|
503
|
+
|
|
504
|
+
# How the svg fragment must be deformed if it is embedded in a container with a different aspect ratio.
|
|
505
|
+
preserve_aspect_ratio: Var[str]
|
|
506
|
+
|
|
507
|
+
# The x coordinate for the reference point of the marker.
|
|
508
|
+
ref_x: Var[str | int | float]
|
|
509
|
+
|
|
510
|
+
# The y coordinate for the reference point of the marker.
|
|
511
|
+
ref_y: Var[str | int | float]
|
|
512
|
+
|
|
513
|
+
# The bound of the SVG viewport for the current SVG fragment.
|
|
514
|
+
view_box: Var[str]
|
|
515
|
+
|
|
516
|
+
|
|
487
517
|
class G(BaseHTML):
|
|
488
518
|
"""The SVG g component, used to group other SVG elements."""
|
|
489
519
|
|
|
@@ -522,6 +552,7 @@ class SVG(ComponentNamespace):
|
|
|
522
552
|
linear_gradient = staticmethod(LinearGradient.create)
|
|
523
553
|
radial_gradient = staticmethod(RadialGradient.create)
|
|
524
554
|
defs = staticmethod(Defs.create)
|
|
555
|
+
marker = staticmethod(Marker.create)
|
|
525
556
|
g = staticmethod(G.create)
|
|
526
557
|
__call__ = staticmethod(Svg.create)
|
|
527
558
|
|
|
@@ -537,6 +568,7 @@ stop = Stop.create
|
|
|
537
568
|
linear_gradient = LinearGradient.create
|
|
538
569
|
radial_gradient = RadialGradient.create
|
|
539
570
|
defs = Defs.create
|
|
571
|
+
marker = Marker.create
|
|
540
572
|
g = G.create
|
|
541
573
|
area = Area.create
|
|
542
574
|
audio = Audio.create
|