Expose Script component from next/script (#1355)
This commit is contained in:
parent
3cbee575fe
commit
bfec196d84
@ -12,6 +12,7 @@ from .app import UploadFile as UploadFile
|
|||||||
from .base import Base as Base
|
from .base import Base as Base
|
||||||
from .compiler.utils import get_asset_path
|
from .compiler.utils import get_asset_path
|
||||||
from .components import *
|
from .components import *
|
||||||
|
from .components.base.script import client_side
|
||||||
from .components.component import custom_component as memo
|
from .components.component import custom_component as memo
|
||||||
from .components.graphing.victory import data as data
|
from .components.graphing.victory import data as data
|
||||||
from .config import Config as Config
|
from .config import Config as Config
|
||||||
|
@ -14,8 +14,8 @@ from reflex.components.base import (
|
|||||||
Image,
|
Image,
|
||||||
Main,
|
Main,
|
||||||
Meta,
|
Meta,
|
||||||
|
NextScript,
|
||||||
RawLink,
|
RawLink,
|
||||||
Script,
|
|
||||||
Title,
|
Title,
|
||||||
)
|
)
|
||||||
from reflex.components.component import Component, ComponentStyle, CustomComponent
|
from reflex.components.component import Component, ComponentStyle, CustomComponent
|
||||||
@ -186,7 +186,7 @@ def create_document_root(stylesheets: List[str]) -> Component:
|
|||||||
Body.create(
|
Body.create(
|
||||||
ColorModeScript.create(),
|
ColorModeScript.create(),
|
||||||
Main.create(),
|
Main.create(),
|
||||||
Script.create(),
|
NextScript.create(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Import all the components."""
|
"""Import all the components."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from .base import ScriptTag
|
from .base import Script
|
||||||
from .component import Component
|
from .component import Component
|
||||||
from .datadisplay import *
|
from .datadisplay import *
|
||||||
from .disclosure import *
|
from .disclosure import *
|
||||||
@ -238,7 +238,7 @@ highlight = Highlight.create
|
|||||||
markdown = Markdown.create
|
markdown = Markdown.create
|
||||||
span = Span.create
|
span = Span.create
|
||||||
text = Text.create
|
text = Text.create
|
||||||
script = ScriptTag.create
|
script = Script.create
|
||||||
aspect_ratio = AspectRatio.create
|
aspect_ratio = AspectRatio.create
|
||||||
kbd = KeyboardKey.create
|
kbd = KeyboardKey.create
|
||||||
color_mode_button = ColorModeButton.create
|
color_mode_button = ColorModeButton.create
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
"""Base components."""
|
"""Base components."""
|
||||||
|
|
||||||
from .body import Body
|
from .body import Body
|
||||||
from .document import ColorModeScript, DocumentHead, Html, Main, Script
|
from .document import ColorModeScript, DocumentHead, Html, Main, NextScript
|
||||||
from .head import Head
|
from .head import Head
|
||||||
from .link import RawLink, ScriptTag
|
from .link import RawLink, ScriptTag
|
||||||
from .meta import Description, Image, Meta, Title
|
from .meta import Description, Image, Meta, Title
|
||||||
|
from .script import Script
|
||||||
|
@ -28,7 +28,7 @@ class Main(NextDocumentLib):
|
|||||||
tag = "Main"
|
tag = "Main"
|
||||||
|
|
||||||
|
|
||||||
class Script(NextDocumentLib):
|
class NextScript(NextDocumentLib):
|
||||||
"""The document main scripts."""
|
"""The document main scripts."""
|
||||||
|
|
||||||
tag = "NextScript"
|
tag = "NextScript"
|
||||||
|
83
reflex/components/base/script.py
Normal file
83
reflex/components/base/script.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
"""Next.js script wrappers and inline script functionality.
|
||||||
|
|
||||||
|
https://nextjs.org/docs/app/api-reference/components/script
|
||||||
|
"""
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
from reflex.components.component import Component
|
||||||
|
from reflex.event import EventChain
|
||||||
|
from reflex.vars import BaseVar, Var
|
||||||
|
|
||||||
|
|
||||||
|
class Script(Component):
|
||||||
|
"""Next.js script component.
|
||||||
|
|
||||||
|
Note that this component differs from reflex.components.base.document.NextScript
|
||||||
|
in that it is intended for use with custom and user-defined scripts.
|
||||||
|
|
||||||
|
It also differs from reflex.components.base.link.ScriptTag, which is the plain
|
||||||
|
HTML <script> tag which does not work when rendering a component.
|
||||||
|
"""
|
||||||
|
|
||||||
|
library = "next/script"
|
||||||
|
tag = "Script"
|
||||||
|
is_default = True
|
||||||
|
|
||||||
|
# Required unless inline script is used
|
||||||
|
src: Var[str]
|
||||||
|
|
||||||
|
# When the script will execute: afterInteractive | beforeInteractive | lazyOnload
|
||||||
|
strategy: Var[str] = "afterInteractive" # type: ignore
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, *children, **props) -> Component:
|
||||||
|
"""Create an inline or user-defined script.
|
||||||
|
|
||||||
|
If a string is provided as the first child, it will be rendered as an inline script
|
||||||
|
otherwise the `src` prop must be provided.
|
||||||
|
|
||||||
|
The following event triggers are provided:
|
||||||
|
|
||||||
|
on_load: Execute code after the script has finished loading.
|
||||||
|
on_ready: Execute code after the script has finished loading and every
|
||||||
|
time the component is mounted.
|
||||||
|
on_error: Execute code if the script fails to load.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*children: The children of the component.
|
||||||
|
**props: The props of the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The component.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: when neither children nor `src` are specified.
|
||||||
|
"""
|
||||||
|
if not children and not props.get("src"):
|
||||||
|
raise ValueError("Must provide inline script or `src` prop.")
|
||||||
|
return super().create(*children, **props)
|
||||||
|
|
||||||
|
def get_triggers(self) -> Set[str]:
|
||||||
|
"""Get the event triggers for the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The event triggers.
|
||||||
|
"""
|
||||||
|
return super().get_triggers() | {"on_load", "on_ready", "on_error"}
|
||||||
|
|
||||||
|
|
||||||
|
def client_side(javascript_code) -> Var[EventChain]:
|
||||||
|
"""Create an event handler that executes arbitrary javascript code.
|
||||||
|
|
||||||
|
The provided code will have access to `args`, which come from the event itself.
|
||||||
|
The code may call functions or reference variables defined in a previously
|
||||||
|
included rx.script function.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
javascript_code: The code to execute.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An EventChain, passable to any component, that will execute the client side javascript
|
||||||
|
when triggered.
|
||||||
|
"""
|
||||||
|
return BaseVar(name=f"...args => {{{javascript_code}}}", type_=EventChain)
|
69
tests/components/base/test_script.py
Normal file
69
tests/components/base/test_script.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
"""Test that Script from next/script renders correctly."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from reflex.components.base.script import Script
|
||||||
|
from reflex.state import State
|
||||||
|
|
||||||
|
|
||||||
|
def test_script_inline():
|
||||||
|
"""Test inline scripts are rendered as children."""
|
||||||
|
component = Script.create("let x = 42")
|
||||||
|
render_dict = component.render()
|
||||||
|
assert render_dict["name"] == "Script"
|
||||||
|
assert not render_dict["contents"]
|
||||||
|
assert len(render_dict["children"]) == 1
|
||||||
|
assert render_dict["children"][0]["contents"] == "{`let x = 42`}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_script_src():
|
||||||
|
"""Test src prop is rendered without children."""
|
||||||
|
component = Script.create(src="foo.js")
|
||||||
|
render_dict = component.render()
|
||||||
|
assert render_dict["name"] == "Script"
|
||||||
|
assert not render_dict["contents"]
|
||||||
|
assert not render_dict["children"]
|
||||||
|
assert 'src="foo.js"' in render_dict["props"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_script_neither():
|
||||||
|
"""Specifying neither children nor src is a ValueError."""
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
Script.create()
|
||||||
|
|
||||||
|
|
||||||
|
class EvState(State):
|
||||||
|
"""State for testing event handlers."""
|
||||||
|
|
||||||
|
def on_ready(self):
|
||||||
|
"""Empty event handler."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_load(self):
|
||||||
|
"""Empty event handler."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_error(self):
|
||||||
|
"""Empty event handler."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_script_event_handler():
|
||||||
|
"""Test event handlers are rendered as expected."""
|
||||||
|
component = Script.create(
|
||||||
|
src="foo.js",
|
||||||
|
on_ready=EvState.on_ready,
|
||||||
|
on_load=EvState.on_load,
|
||||||
|
on_error=EvState.on_error,
|
||||||
|
)
|
||||||
|
render_dict = component.render()
|
||||||
|
assert (
|
||||||
|
'onReady={_e => Event([E("ev_state.on_ready", {})], _e)}'
|
||||||
|
in render_dict["props"]
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
'onLoad={_e => Event([E("ev_state.on_load", {})], _e)}' in render_dict["props"]
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
'onError={_e => Event([E("ev_state.on_error", {})], _e)}'
|
||||||
|
in render_dict["props"]
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user