Add the onvisible event handler to Element (#2911)
* Add the `onvisible` event using the IntersectionObserver * Add a visible example This example is based on https://codepen.io/ryanfinni/pen/VwZeGxN * Remove web-sys CustomEvent feature for html package * update js bindings and hash * Use dioxus doc instead of lorem ipsum * Fix clippy warning * Add get_target method to the event data * Make `get_time` return an Instant instead of a f64 * fix merge conflicts, compilinga * remove target * small nits * serialzie as time_ms * fix timing, works on desktop * remove latent nits, fix hash * box size clippy * fix browserlist cargo.lock --------- Co-authored-by: Evan Almloff <evanalmloff@gmail.com> Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
20 files changed
+961
-289
No files matched your search
Generated
+325
-271
File diff suppressed because it is too large.
Load diff
@@ -436,6 +436,10 @@ doc-scrape-examples = true
|
||||
name = "streams"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "visible"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "window_event"
|
||||
required-features = ["desktop"]
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.container {
|
||||
height: 500px;
|
||||
width: 500px;
|
||||
margin: 100px auto;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 30px;
|
||||
|
||||
}
|
||||
|
||||
.animated-text {
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
transform: translateY(100px);
|
||||
transition: opacity 1s ease, transform 1s ease;
|
||||
}
|
||||
|
||||
.animated-text.visible {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
//! A simple little clock that updates the time every few milliseconds.
|
||||
//!
|
||||
|
||||
use async_std::task::sleep;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
//! Port of the https://codepen.io/ryanfinni/pen/VwZeGxN example
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut animated_classes = use_signal(|| ["animated-text", ""]);
|
||||
|
||||
rsx! {
|
||||
document::Link { rel: "stylesheet", href: asset!("./examples/assets/visible.css") }
|
||||
|
||||
div {
|
||||
class: "container",
|
||||
|
||||
p {
|
||||
"Scroll to the bottom of the page. The text will transition in when it becomes visible in the viewport."
|
||||
}
|
||||
|
||||
p {
|
||||
"First, let's create a new project for our hacker news app. We can use the CLI to create a new
|
||||
project. You can select a platform of your choice or view the getting started guide for more information
|
||||
on each option. If you aren't sure what platform to try out, we recommend getting started with web or
|
||||
desktop:"
|
||||
}
|
||||
|
||||
p {
|
||||
"The template contains some boilerplate to help you get started. For this guide, we will be rebuilding some of the code
|
||||
from scratch for learning purposes. You can clear the src/main.rs file. We will be adding new code in the next
|
||||
sections."
|
||||
}
|
||||
|
||||
p {
|
||||
"Next, let's setup our dependencies. We need to set up a few dependencies to work with the hacker news API: "
|
||||
}
|
||||
|
||||
p {
|
||||
"First, let's create a new project for our hacker news app. We can use the CLI to create a new
|
||||
project. You can select a platform of your choice or view the getting started guide for more information
|
||||
on each option. If you aren't sure what platform to try out, we recommend getting started with web or
|
||||
desktop:"
|
||||
}
|
||||
|
||||
p {
|
||||
"The template contains some boilerplate to help you get started. For this guide, we will be rebuilding some of the code
|
||||
from scratch for learning purposes. You can clear the src/main.rs file. We will be adding new code in the next
|
||||
sections."
|
||||
}
|
||||
|
||||
p {
|
||||
"Next, let's setup our dependencies. We need to set up a few dependencies to work with the hacker news API: "
|
||||
}
|
||||
|
||||
p {
|
||||
"First, let's create a new project for our hacker news app. We can use the CLI to create a new
|
||||
project. You can select a platform of your choice or view the getting started guide for more information
|
||||
on each option. If you aren't sure what platform to try out, we recommend getting started with web or
|
||||
desktop:"
|
||||
}
|
||||
|
||||
p {
|
||||
"The template contains some boilerplate to help you get started. For this guide, we will be rebuilding some of the code
|
||||
from scratch for learning purposes. You can clear the src/main.rs file. We will be adding new code in the next
|
||||
sections."
|
||||
}
|
||||
|
||||
p {
|
||||
"Next, let's setup our dependencies. We need to set up a few dependencies to work with the hacker news API: "
|
||||
}
|
||||
|
||||
h2 {
|
||||
class: animated_classes().join(" "),
|
||||
onvisible: move |evt| {
|
||||
let data = evt.data();
|
||||
if let Ok(is_intersecting) = data.is_intersecting() {
|
||||
animated_classes.write()[1] = if is_intersecting { "visible" } else { "" };
|
||||
}
|
||||
},
|
||||
|
||||
"Animated Text"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,7 @@ pub fn event_bubbles(evt: &str) -> bool {
|
||||
"transitionend" => true,
|
||||
"toggle" => true,
|
||||
"mounted" => false,
|
||||
"visible" => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
@@ -161,6 +161,14 @@ impl HtmlEventConverter for SerializedHtmlEventConverter {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn convert_visible_data(&self, event: &PlatformEventData) -> VisibleData {
|
||||
event
|
||||
.downcast::<SerializedVisibleData>()
|
||||
.cloned()
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn convert_wheel_data(&self, event: &PlatformEventData) -> WheelData {
|
||||
event
|
||||
.downcast::<SerializedWheelData>()
|
||||
|
||||
@@ -146,6 +146,8 @@ pub trait HtmlEventConverter: Send + Sync {
|
||||
fn convert_touch_data(&self, event: &PlatformEventData) -> TouchData;
|
||||
/// Convert a general event to a transition data event
|
||||
fn convert_transition_data(&self, event: &PlatformEventData) -> TransitionData;
|
||||
/// Convert a general event to a visible data event
|
||||
fn convert_visible_data(&self, event: &PlatformEventData) -> VisibleData;
|
||||
/// Convert a general event to a wheel data event
|
||||
fn convert_wheel_data(&self, event: &PlatformEventData) -> WheelData;
|
||||
}
|
||||
@@ -258,6 +260,12 @@ impl From<&PlatformEventData> for TransitionData {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PlatformEventData> for VisibleData {
|
||||
fn from(val: &PlatformEventData) -> Self {
|
||||
with_event_converter(|c| c.convert_visible_data(val))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PlatformEventData> for WheelData {
|
||||
fn from(val: &PlatformEventData) -> Self {
|
||||
with_event_converter(|c| c.convert_wheel_data(val))
|
||||
@@ -282,6 +290,7 @@ mod selection;
|
||||
mod toggle;
|
||||
mod touch;
|
||||
mod transition;
|
||||
mod visible;
|
||||
mod wheel;
|
||||
|
||||
pub use animation::*;
|
||||
@@ -302,4 +311,5 @@ pub use selection::*;
|
||||
pub use toggle::*;
|
||||
pub use touch::*;
|
||||
pub use transition::*;
|
||||
pub use visible::*;
|
||||
pub use wheel::*;
|
||||
@@ -0,0 +1,307 @@
|
||||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
pub struct VisibleData {
|
||||
inner: Box<dyn HasVisibleData>,
|
||||
}
|
||||
|
||||
impl<E: HasVisibleData> From<E> for VisibleData {
|
||||
fn from(e: E) -> Self {
|
||||
Self { inner: Box::new(e) }
|
||||
}
|
||||
}
|
||||
|
||||
impl VisibleData {
|
||||
/// Create a new VisibleData
|
||||
pub fn new(inner: impl HasVisibleData + 'static) -> Self {
|
||||
Self {
|
||||
inner: Box::new(inner),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the bounds rectangle of the target element
|
||||
pub fn get_bounding_client_rect(&self) -> VisibleResult<PixelsRect> {
|
||||
self.inner.get_bounding_client_rect()
|
||||
}
|
||||
|
||||
/// Get the ratio of the intersectionRect to the boundingClientRect
|
||||
pub fn get_intersection_ratio(&self) -> VisibleResult<f64> {
|
||||
self.inner.get_intersection_ratio()
|
||||
}
|
||||
|
||||
/// Get the rect representing the target's visible area
|
||||
pub fn get_intersection_rect(&self) -> VisibleResult<PixelsRect> {
|
||||
self.inner.get_intersection_rect()
|
||||
}
|
||||
|
||||
/// Get if the target element intersects with the intersection observer's root
|
||||
pub fn is_intersecting(&self) -> VisibleResult<bool> {
|
||||
self.inner.is_intersecting()
|
||||
}
|
||||
|
||||
/// Get the rect for the intersection observer's root
|
||||
pub fn get_root_bounds(&self) -> VisibleResult<PixelsRect> {
|
||||
self.inner.get_root_bounds()
|
||||
}
|
||||
|
||||
/// Get a timestamp indicating the time at which the intersection was recorded
|
||||
pub fn get_time(&self) -> VisibleResult<SystemTime> {
|
||||
self.inner.get_time()
|
||||
}
|
||||
|
||||
/// Downcast this event to a concrete event type
|
||||
pub fn downcast<T: 'static>(&self) -> Option<&T> {
|
||||
self.inner.as_any().downcast_ref::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for VisibleData {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("VisibleData")
|
||||
.field(
|
||||
"bounding_client_rect",
|
||||
&self.inner.get_bounding_client_rect(),
|
||||
)
|
||||
.field("intersection_ratio", &self.inner.get_intersection_ratio())
|
||||
.field("intersection_rect", &self.inner.get_intersection_rect())
|
||||
.field("is_intersecting", &self.inner.is_intersecting())
|
||||
.field("root_bounds", &self.inner.get_root_bounds())
|
||||
.field("time", &self.inner.get_time())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for VisibleData {
|
||||
fn eq(&self, _: &Self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct DOMRect {
|
||||
bottom: f64, // The bottom coordinate value of the DOMRect (usually the same as y + height)
|
||||
height: f64, // The height of the DOMRect
|
||||
left: f64, // The left coordinate value of the DOMRect (usually the same as x)
|
||||
right: f64, // The right coordinate value of the DOMRect (usually the same as x + width)
|
||||
top: f64, // The top coordinate value of the DOMRect (usually the same as y)
|
||||
width: f64, // The width of the DOMRect
|
||||
x: f64, // The x coordinate of the DOMRect's origin
|
||||
y: f64, // The y coordinate of the DOMRect's origin
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl From<PixelsRect> for DOMRect {
|
||||
fn from(rect: PixelsRect) -> Self {
|
||||
let x = rect.origin.x;
|
||||
let y = rect.origin.y;
|
||||
let height = rect.height();
|
||||
let width = rect.width();
|
||||
|
||||
Self {
|
||||
bottom: y + height,
|
||||
height,
|
||||
left: x,
|
||||
right: x + width,
|
||||
top: y,
|
||||
width,
|
||||
x,
|
||||
y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl From<&DOMRect> for PixelsRect {
|
||||
fn from(rect: &DOMRect) -> Self {
|
||||
PixelsRect::new(
|
||||
euclid::Point2D::new(rect.x, rect.y),
|
||||
euclid::Size2D::new(rect.width, rect.height),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
/// A serialized version of VisibleData
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct SerializedVisibleData {
|
||||
pub bounding_client_rect: DOMRect,
|
||||
pub intersection_ratio: f64,
|
||||
pub intersection_rect: DOMRect,
|
||||
pub is_intersecting: bool,
|
||||
pub root_bounds: DOMRect,
|
||||
pub time_ms: u128,
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl SerializedVisibleData {
|
||||
/// Create a new SerializedVisibleData
|
||||
pub fn new(
|
||||
bounding_client_rect: DOMRect,
|
||||
intersection_ratio: f64,
|
||||
intersection_rect: DOMRect,
|
||||
is_intersecting: bool,
|
||||
root_bounds: DOMRect,
|
||||
time_ms: u128,
|
||||
) -> Self {
|
||||
Self {
|
||||
bounding_client_rect,
|
||||
intersection_ratio,
|
||||
intersection_rect,
|
||||
is_intersecting,
|
||||
root_bounds,
|
||||
time_ms,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl From<&VisibleData> for SerializedVisibleData {
|
||||
fn from(data: &VisibleData) -> Self {
|
||||
Self::new(
|
||||
data.get_bounding_client_rect().unwrap().into(),
|
||||
data.get_intersection_ratio().unwrap(),
|
||||
data.get_intersection_rect().unwrap().into(),
|
||||
data.is_intersecting().unwrap(),
|
||||
data.get_root_bounds().unwrap().into(),
|
||||
data.get_time().unwrap().elapsed().unwrap().as_millis(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl HasVisibleData for SerializedVisibleData {
|
||||
/// Get the bounds rectangle of the target element
|
||||
fn get_bounding_client_rect(&self) -> VisibleResult<PixelsRect> {
|
||||
Ok((&self.bounding_client_rect).into())
|
||||
}
|
||||
|
||||
/// Get the ratio of the intersectionRect to the boundingClientRect
|
||||
fn get_intersection_ratio(&self) -> VisibleResult<f64> {
|
||||
Ok(self.intersection_ratio)
|
||||
}
|
||||
|
||||
/// Get the rect representing the target's visible area
|
||||
fn get_intersection_rect(&self) -> VisibleResult<PixelsRect> {
|
||||
Ok((&self.intersection_rect).into())
|
||||
}
|
||||
|
||||
/// Get if the target element intersects with the intersection observer's root
|
||||
fn is_intersecting(&self) -> VisibleResult<bool> {
|
||||
Ok(self.is_intersecting)
|
||||
}
|
||||
|
||||
/// Get the rect for the intersection observer's root
|
||||
fn get_root_bounds(&self) -> VisibleResult<PixelsRect> {
|
||||
Ok((&self.root_bounds).into())
|
||||
}
|
||||
|
||||
/// Get a timestamp indicating the time at which the intersection was recorded
|
||||
fn get_time(&self) -> VisibleResult<SystemTime> {
|
||||
Ok(SystemTime::UNIX_EPOCH + std::time::Duration::from_millis(self.time_ms as u64))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl serde::Serialize for VisibleData {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
SerializedVisibleData::from(self).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl<'de> serde::Deserialize<'de> for VisibleData {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let data = SerializedVisibleData::deserialize(deserializer)?;
|
||||
Ok(Self {
|
||||
inner: Box::new(data),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasVisibleData: std::any::Any {
|
||||
/// Get the bounds rectangle of the target element
|
||||
fn get_bounding_client_rect(&self) -> VisibleResult<PixelsRect> {
|
||||
Err(VisibleError::NotSupported)
|
||||
}
|
||||
|
||||
/// Get the ratio of the intersectionRect to the boundingClientRect
|
||||
fn get_intersection_ratio(&self) -> VisibleResult<f64> {
|
||||
Err(VisibleError::NotSupported)
|
||||
}
|
||||
|
||||
/// Get the rect representing the target's visible area
|
||||
fn get_intersection_rect(&self) -> VisibleResult<PixelsRect> {
|
||||
Err(VisibleError::NotSupported)
|
||||
}
|
||||
|
||||
/// Get if the target element intersects with the intersection observer's root
|
||||
fn is_intersecting(&self) -> VisibleResult<bool> {
|
||||
Err(VisibleError::NotSupported)
|
||||
}
|
||||
|
||||
/// Get the rect for the intersection observer's root
|
||||
fn get_root_bounds(&self) -> VisibleResult<PixelsRect> {
|
||||
Err(VisibleError::NotSupported)
|
||||
}
|
||||
|
||||
/// Get a timestamp indicating the time at which the intersection was recorded
|
||||
fn get_time(&self) -> VisibleResult<SystemTime> {
|
||||
Err(VisibleError::NotSupported)
|
||||
}
|
||||
|
||||
/// return self as Any
|
||||
fn as_any(&self) -> &dyn std::any::Any;
|
||||
}
|
||||
|
||||
use dioxus_core::Event;
|
||||
|
||||
use crate::geometry::PixelsRect;
|
||||
|
||||
pub type VisibleEvent = Event<VisibleData>;
|
||||
|
||||
impl_event! {
|
||||
VisibleData;
|
||||
|
||||
/// onvisible
|
||||
onvisible
|
||||
}
|
||||
|
||||
/// The VisibleResult type for the VisibleData
|
||||
pub type VisibleResult<T> = Result<T, VisibleError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// The error type for the VisibleData
|
||||
#[non_exhaustive]
|
||||
pub enum VisibleError {
|
||||
/// The renderer does not support the requested operation
|
||||
NotSupported,
|
||||
/// The element was not found
|
||||
OperationFailed(Box<dyn std::error::Error>),
|
||||
/// The target element had no associated ElementId
|
||||
NoElementId,
|
||||
}
|
||||
|
||||
impl Display for VisibleError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VisibleError::NotSupported => {
|
||||
write!(f, "The renderer does not support the requested operation")
|
||||
}
|
||||
VisibleError::OperationFailed(e) => {
|
||||
write!(f, "The operation failed: {}", e)
|
||||
}
|
||||
VisibleError::NoElementId => {
|
||||
write!(f, "The target had no associated ElementId")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for VisibleError {}
|
||||
@@ -105,6 +105,9 @@ fn deserialize_raw(name: &str, data: &serde_json::Value) -> Result<EventData, se
|
||||
// Scroll
|
||||
"scroll" => Scroll(de(data)?),
|
||||
|
||||
// Visible
|
||||
"visible" => Visible(de(data)?),
|
||||
|
||||
// Wheel
|
||||
"wheel" => Wheel(de(data)?),
|
||||
|
||||
@@ -163,6 +166,7 @@ pub enum EventData {
|
||||
Touch(SerializedTouchData),
|
||||
Resize(SerializedResizeData),
|
||||
Scroll(SerializedScrollData),
|
||||
Visible(SerializedVisibleData),
|
||||
Wheel(SerializedWheelData),
|
||||
Media(SerializedMediaData),
|
||||
Animation(SerializedAnimationData),
|
||||
@@ -207,6 +211,9 @@ impl EventData {
|
||||
EventData::Scroll(data) => {
|
||||
Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>
|
||||
}
|
||||
EventData::Visible(data) => {
|
||||
Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>
|
||||
}
|
||||
EventData::Wheel(data) => {
|
||||
Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>
|
||||
}
|
||||
@@ -415,6 +422,14 @@ impl HtmlEventConverter for SerializedHtmlEventConverter {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn convert_visible_data(&self, event: &PlatformEventData) -> VisibleData {
|
||||
event
|
||||
.downcast::<SerializedVisibleData>()
|
||||
.cloned()
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn convert_wheel_data(&self, event: &PlatformEventData) -> WheelData {
|
||||
event
|
||||
.downcast::<SerializedWheelData>()
|
||||
|
||||
@@ -1 +1 @@
|
||||
function setAttributeInner(node,field,value,ns){if(ns==="style"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case"value":if(node.value!==value)node.value=value;break;case"initial_value":node.defaultValue=value;break;case"checked":node.checked=truthy(value);break;case"initial_checked":node.defaultChecked=truthy(value);break;case"selected":node.selected=truthy(value);break;case"initial_selected":node.defaultSelected=truthy(value);break;case"dangerous_inner_html":node.innerHTML=value;break;default:if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)}}var truthy=function(val){return val==="true"||val===!0},isBoolAttr=function(field){switch(field){case"allowfullscreen":case"allowpaymentrequest":case"async":case"autofocus":case"autoplay":case"checked":case"controls":case"default":case"defer":case"disabled":case"formnovalidate":case"hidden":case"ismap":case"itemscope":case"loop":case"multiple":case"muted":case"nomodule":case"novalidate":case"open":case"playsinline":case"readonly":case"required":case"reversed":case"selected":case"truespeed":case"webkitdirectory":return!0;default:return!1}};class BaseInterpreter{global;local;root;handler;resizeObserver;nodes;stack;templates;m;constructor(){}initialize(root,handler=null){this.global={},this.local={},this.root=root,this.nodes=[root],this.stack=[root],this.templates={},this.handler=handler}handleResizeEvent(entry){const target=entry.target;let event=new CustomEvent("resize",{bubbles:!1,detail:entry});target.dispatchEvent(event)}createObserver(element){if(!this.resizeObserver)this.resizeObserver=new ResizeObserver((entries)=>{for(let entry of entries)this.handleResizeEvent(entry)});this.resizeObserver.observe(element)}removeObserver(element){if(this.resizeObserver)this.resizeObserver.unobserve(element)}createListener(event_name,element,bubbles){if(bubbles)if(this.global[event_name]===void 0)this.global[event_name]={active:1,callback:this.handler},this.root.addEventListener(event_name,this.handler);else this.global[event_name].active++;else{const id=element.getAttribute("data-dioxus-id");if(!this.local[id])this.local[id]={};element.addEventListener(event_name,this.handler)}if(event_name=="resize")this.createObserver(element)}removeListener(element,event_name,bubbles){if(bubbles)this.removeBubblingListener(event_name);else this.removeNonBubblingListener(element,event_name)}removeBubblingListener(event_name){if(this.global[event_name].active--,this.global[event_name].active===0)this.root.removeEventListener(event_name,this.global[event_name].callback),delete this.global[event_name]}removeNonBubblingListener(element,event_name){const id=element.getAttribute("data-dioxus-id");if(delete this.local[id][event_name],Object.keys(this.local[id]).length===0)delete this.local[id];element.removeEventListener(event_name,this.handler)}removeAllNonBubblingListeners(element){const id=element.getAttribute("data-dioxus-id");delete this.local[id]}getNode(id){return this.nodes[id]}pushRoot(node){this.stack.push(node)}appendChildren(id,many){const root=this.nodes[id],els=this.stack.splice(this.stack.length-many);for(let k=0;k<many;k++)root.appendChild(els[k])}loadChild(ptr,len){let node=this.stack[this.stack.length-1],ptr_end=ptr+len;for(;ptr<ptr_end;ptr++){let end=this.m.getUint8(ptr);for(node=node.firstChild;end>0;end--)node=node.nextSibling}return node}saveTemplate(nodes,tmpl_id){this.templates[tmpl_id]=nodes}hydrate_node(hydrateNode,ids){const split=hydrateNode.getAttribute("data-node-hydration").split(","),id=ids[parseInt(split[0])];if(this.nodes[id]=hydrateNode,split.length>1){hydrateNode.listening=split.length-1,hydrateNode.setAttribute("data-dioxus-id",id.toString());for(let j=1;j<split.length;j++){const split2=split[j].split(":"),event_name=split2[0],bubbles=split2[1]==="1";this.createListener(event_name,hydrateNode,bubbles)}}}hydrate(ids,underNodes){for(let i=0;i<underNodes.length;i++){const under=underNodes[i];if(under instanceof HTMLElement){if(under.getAttribute("data-node-hydration"))this.hydrate_node(under,ids);const hydrateNodes=under.querySelectorAll("[data-node-hydration]");for(let i2=0;i2<hydrateNodes.length;i2++)this.hydrate_node(hydrateNodes[i2],ids)}const treeWalker=document.createTreeWalker(under,NodeFilter.SHOW_COMMENT);while(treeWalker.currentNode){const currentNode=treeWalker.currentNode;if(currentNode.nodeType===Node.COMMENT_NODE){const id=currentNode.textContent,placeholderSplit=id.split("placeholder");if(placeholderSplit.length>1){if(this.nodes[ids[parseInt(placeholderSplit[1])]]=currentNode,!treeWalker.nextNode())break;continue}const textNodeSplit=id.split("node-id");if(textNodeSplit.length>1){let next=currentNode.nextSibling;currentNode.remove();let commentAfterText,textNode;if(next.nodeType===Node.COMMENT_NODE){const newText=next.parentElement.insertBefore(document.createTextNode(""),next);commentAfterText=next,textNode=newText}else textNode=next,commLine truncated
|
||||
function setAttributeInner(node,field,value,ns){if(ns==="style"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case"value":if(node.value!==value)node.value=value;break;case"initial_value":node.defaultValue=value;break;case"checked":node.checked=truthy(value);break;case"initial_checked":node.defaultChecked=truthy(value);break;case"selected":node.selected=truthy(value);break;case"initial_selected":node.defaultSelected=truthy(value);break;case"dangerous_inner_html":node.innerHTML=value;break;default:if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)}}var truthy=function(val){return val==="true"||val===!0},isBoolAttr=function(field){switch(field){case"allowfullscreen":case"allowpaymentrequest":case"async":case"autofocus":case"autoplay":case"checked":case"controls":case"default":case"defer":case"disabled":case"formnovalidate":case"hidden":case"ismap":case"itemscope":case"loop":case"multiple":case"muted":case"nomodule":case"novalidate":case"open":case"playsinline":case"readonly":case"required":case"reversed":case"selected":case"truespeed":case"webkitdirectory":return!0;default:return!1}};class BaseInterpreter{global;local;root;handler;resizeObserver;intersectionObserver;nodes;stack;templates;m;constructor(){}initialize(root,handler=null){this.global={},this.local={},this.root=root,this.nodes=[root],this.stack=[root],this.templates={},this.handler=handler}handleResizeEvent(entry){const target=entry.target;let event=new CustomEvent("resize",{bubbles:!1,detail:entry});target.dispatchEvent(event)}createResizeObserver(element){if(!this.resizeObserver)this.resizeObserver=new ResizeObserver((entries)=>{for(let entry of entries)this.handleResizeEvent(entry)});this.resizeObserver.observe(element)}removeResizeObserver(element){if(this.resizeObserver)this.resizeObserver.unobserve(element)}handleIntersectionEvent(entry){const target=entry.target;let event=new CustomEvent("visible",{bubbles:!1,detail:entry});target.dispatchEvent(event)}createIntersectionObserver(element){if(!this.intersectionObserver)this.intersectionObserver=new IntersectionObserver((entries)=>{for(let entry of entries)this.handleIntersectionEvent(entry)});this.intersectionObserver.observe(element)}removeIntersectionObserver(element){if(this.intersectionObserver)this.intersectionObserver.unobserve(element)}createListener(event_name,element,bubbles){if(event_name=="resize")this.createResizeObserver(element);else if(event_name=="visible")this.createIntersectionObserver(element);if(bubbles)if(this.global[event_name]===void 0)this.global[event_name]={active:1,callback:this.handler},this.root.addEventListener(event_name,this.handler);else this.global[event_name].active++;else{const id=element.getAttribute("data-dioxus-id");if(!this.local[id])this.local[id]={};element.addEventListener(event_name,this.handler)}}removeListener(element,event_name,bubbles){if(event_name=="resize")this.removeResizeObserver(element);else if(event_name=="visible")this.removeIntersectionObserver(element);else if(bubbles)this.removeBubblingListener(event_name);else this.removeNonBubblingListener(element,event_name)}removeBubblingListener(event_name){if(this.global[event_name].active--,this.global[event_name].active===0)this.root.removeEventListener(event_name,this.global[event_name].callback),delete this.global[event_name]}removeNonBubblingListener(element,event_name){const id=element.getAttribute("data-dioxus-id");if(delete this.local[id][event_name],Object.keys(this.local[id]).length===0)delete this.local[id];element.removeEventListener(event_name,this.handler)}removeAllNonBubblingListeners(element){const id=element.getAttribute("data-dioxus-id");delete this.local[id]}getNode(id){return this.nodes[id]}pushRoot(node){this.stack.push(node)}appendChildren(id,many){const root=this.nodes[id],els=this.stack.splice(this.stack.length-many);for(let k=0;k<many;k++)root.appendChild(els[k])}loadChild(ptr,len){let node=this.stack[this.stack.length-1],ptr_end=ptr+len;for(;ptr<ptr_end;ptr++){let end=this.m.getUint8(ptr);for(node=node.firstChild;end>0;end--)node=node.nextSibling}return node}saveTemplate(nodes,tmpl_id){this.templates[tmpl_id]=nodes}hydrate_node(hydrateNode,ids){const split=hydrateNode.getAttribute("data-node-hydration").split(","),id=ids[parseInt(split[0])];if(this.nodes[id]=hydrateNode,split.length>1){hydrateNode.listening=split.length-1,hydrateNode.setAttribute("data-dioxus-id",id.toString());for(let j=1;j<split.length;j++){const split2=split[j].split(":"),event_name=split2[0],bubbles=split2[1]==="1";this.createListener(event_name,hydrateNode,bubbles)}}}hydrate(ids,underNodes){for(let i=0;i<underNodes.length;i++){const under=underNodes[i];if(under instanceof HTMLElement){if(under.getAttribute("data-node-hydration"))this.hydrate_node(under,ids);const hydrateNodes=under.querySelectorAll("[data-node-hydration]");for(let i2=0;i2<hydrateNodes.length;i2++)this.hydrate_node(hydrateNLine truncated
|
||||
@@ -1 +1 @@
|
||||
[6449103750905854967, 4461869229701639737, 13069001215487072322, 8716623267269178440, 5336385715226370016, 14456089431355876478, 7422899642446454418, 5052021921702764563, 17534315583914394253, 5638004933879392817]
|
||||
[6449103750905854967, 5429983586723452597, 13069001215487072322, 8716623267269178440, 5336385715226370016, 14456089431355876478, 7422899642446454418, 5052021921702764563, 12925655762638175824, 5638004933879392817]
|
||||
@@ -1 +1 @@
|
||||
function retrieveValues(event,target){let contents={values:{}},form=target.closest("form");if(form){if(event.type==="input"||event.type==="change"||event.type==="submit"||event.type==="reset"||event.type==="click")contents=retrieveFormValues(form)}return contents}function retrieveFormValues(form){const formData=new FormData(form),contents={};return formData.forEach((value,key)=>{if(contents[key])contents[key].push(value);else contents[key]=[value]}),{valid:form.checkValidity(),values:contents}}function retrieveSelectValue(target){let options=target.selectedOptions,values=[];for(let i=0;i<options.length;i++)values.push(options[i].value);return values}function serializeEvent(event,target){let contents={},extend=(obj)=>contents={...contents,...obj};if(event instanceof WheelEvent)extend(serializeWheelEvent(event));if(event instanceof MouseEvent)extend(serializeMouseEvent(event));if(event instanceof KeyboardEvent)extend(serializeKeyboardEvent(event));if(event instanceof InputEvent)extend(serializeInputEvent(event,target));if(event instanceof PointerEvent)extend(serializePointerEvent(event));if(event instanceof AnimationEvent)extend(serializeAnimationEvent(event));if(event instanceof TransitionEvent)extend({property_name:event.propertyName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement});if(event instanceof CompositionEvent)extend({data:event.data});if(event instanceof DragEvent)extend(serializeDragEvent(event));if(event instanceof FocusEvent)extend({});if(event instanceof ClipboardEvent)extend({});if(event instanceof CustomEvent){const detail=event.detail;if(detail instanceof ResizeObserverEntry)extend(serializeResizeEventDetail(detail))}if(typeof TouchEvent!=="undefined"&&event instanceof TouchEvent)extend(serializeTouchEvent(event));if(event.type==="submit"||event.type==="reset"||event.type==="click"||event.type==="change"||event.type==="input")extend(serializeInputEvent(event,target));if(event instanceof DragEvent);return contents}var toSerializableResizeObserverSize=function(size,is_inline_width){return[is_inline_width?size.inlineSize:size.blockSize,is_inline_width?size.blockSize:size.inlineSize]};function serializeResizeEventDetail(detail){let is_inline_width=!0;if(detail.target instanceof HTMLElement){if(window.getComputedStyle(detail.target).getPropertyValue("writing-mode")!=="horizontal-tb")is_inline_width=!1}return{border_box_size:detail.borderBoxSize!==void 0?toSerializableResizeObserverSize(detail.borderBoxSize[0],is_inline_width):detail.contentRect,content_box_size:detail.contentBoxSize!==void 0?toSerializableResizeObserverSize(detail.contentBoxSize[0],is_inline_width):detail.contentRect,content_rect:detail.contentRect}}var serializeInputEvent=function(event,target){let contents={};if(target instanceof HTMLElement){let values=retrieveValues(event,target);contents.values=values.values,contents.valid=values.valid}if(event.target instanceof HTMLInputElement){let target2=event.target,value=target2.value??target2.textContent??"";if(target2.type==="checkbox")value=target2.checked?"true":"false";else if(target2.type==="radio")value=target2.value;contents.value=value}if(event.target instanceof HTMLTextAreaElement)contents.value=event.target.value;if(event.target instanceof HTMLSelectElement)contents.value=retrieveSelectValue(event.target).join(",");if(contents.value===void 0)contents.value="";return contents},serializeWheelEvent=function(event){return{delta_x:event.deltaX,delta_y:event.deltaY,delta_z:event.deltaZ,delta_mode:event.deltaMode}},serializeTouchEvent=function(event){return{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,changed_touches:event.changedTouches,target_touches:event.targetTouches,touches:event.touches}},serializePointerEvent=function(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey,pointer_id:event.pointerId,width:event.width,height:event.height,pressure:event.pressure,tangential_pressure:event.tangentialPressure,tilt_x:event.tiltX,tilt_y:event.tiltY,twist:event.twist,pointer_type:event.pointerType,is_primary:event.isPrimary}},serializeMouseEvent=function(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,offset_x:event.offsetX,offset_y:event.offsetY,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey}},serializeKeyboardEvent=function(event){return{char_code:event.charCode,is_composing:event.isComposing,key:event.key,alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,key_code:event.keyCode,shift_key:event.shiftKey,location:event.location,repeat:event.repeat,which:event.which,code:event.code}},serializeAnimationELine truncated
|
||||
function retrieveValues(event,target){let contents={values:{}},form=target.closest("form");if(form){if(event.type==="input"||event.type==="change"||event.type==="submit"||event.type==="reset"||event.type==="click")contents=retrieveFormValues(form)}return contents}function retrieveFormValues(form){const formData=new FormData(form),contents={};return formData.forEach((value,key)=>{if(contents[key])contents[key].push(value);else contents[key]=[value]}),{valid:form.checkValidity(),values:contents}}function retrieveSelectValue(target){let options=target.selectedOptions,values=[];for(let i=0;i<options.length;i++)values.push(options[i].value);return values}function serializeEvent(event,target){let contents={},extend=(obj)=>contents={...contents,...obj};if(event instanceof WheelEvent)extend(serializeWheelEvent(event));if(event instanceof MouseEvent)extend(serializeMouseEvent(event));if(event instanceof KeyboardEvent)extend(serializeKeyboardEvent(event));if(event instanceof InputEvent)extend(serializeInputEvent(event,target));if(event instanceof PointerEvent)extend(serializePointerEvent(event));if(event instanceof AnimationEvent)extend(serializeAnimationEvent(event));if(event instanceof TransitionEvent)extend({property_name:event.propertyName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement});if(event instanceof CompositionEvent)extend({data:event.data});if(event instanceof DragEvent)extend(serializeDragEvent(event));if(event instanceof FocusEvent)extend({});if(event instanceof ClipboardEvent)extend({});if(event instanceof CustomEvent){const detail=event.detail;if(detail instanceof ResizeObserverEntry)extend(serializeResizeEventDetail(detail));else if(detail instanceof IntersectionObserverEntry)extend(serializeIntersectionEventDetail(detail))}if(typeof TouchEvent!=="undefined"&&event instanceof TouchEvent)extend(serializeTouchEvent(event));if(event.type==="submit"||event.type==="reset"||event.type==="click"||event.type==="change"||event.type==="input")extend(serializeInputEvent(event,target));if(event instanceof DragEvent);return contents}var toSerializableResizeObserverSize=function(size,is_inline_width){return[is_inline_width?size.inlineSize:size.blockSize,is_inline_width?size.blockSize:size.inlineSize]};function serializeResizeEventDetail(detail){let is_inline_width=!0;if(detail.target instanceof HTMLElement){if(window.getComputedStyle(detail.target).getPropertyValue("writing-mode")!=="horizontal-tb")is_inline_width=!1}return{border_box_size:detail.borderBoxSize!==void 0?toSerializableResizeObserverSize(detail.borderBoxSize[0],is_inline_width):detail.contentRect,content_box_size:detail.contentBoxSize!==void 0?toSerializableResizeObserverSize(detail.contentBoxSize[0],is_inline_width):detail.contentRect,content_rect:detail.contentRect}}function serializeIntersectionEventDetail(detail){return{bounding_client_rect:detail.boundingClientRect,intersection_ratio:detail.intersectionRatio,intersection_rect:detail.intersectionRect,is_intersecting:detail.isIntersecting,root_bounds:detail.rootBounds,time_ms:Math.floor(Date.now()+detail.time)}}var serializeInputEvent=function(event,target){let contents={};if(target instanceof HTMLElement){let values=retrieveValues(event,target);contents.values=values.values,contents.valid=values.valid}if(event.target instanceof HTMLInputElement){let target2=event.target,value=target2.value??target2.textContent??"";if(target2.type==="checkbox")value=target2.checked?"true":"false";else if(target2.type==="radio")value=target2.value;contents.value=value}if(event.target instanceof HTMLTextAreaElement)contents.value=event.target.value;if(event.target instanceof HTMLSelectElement)contents.value=retrieveSelectValue(event.target).join(",");if(contents.value===void 0)contents.value="";return contents},serializeWheelEvent=function(event){return{delta_x:event.deltaX,delta_y:event.deltaY,delta_z:event.deltaZ,delta_mode:event.deltaMode}},serializeTouchEvent=function(event){return{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,changed_touches:event.changedTouches,target_touches:event.targetTouches,touches:event.touches}},serializePointerEvent=function(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey,pointer_id:event.pointerId,width:event.width,height:event.height,pressure:event.pressure,tangential_pressure:event.tangentialPressure,tilt_x:event.tiltX,tilt_y:event.tiltY,twist:event.twist,pointer_type:event.pointerType,is_primary:event.isPrimary}},serializeMouseEvent=function(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,offset_x:event.offsetX,offset_y:event.offsetY,page_x:event.pageX,page_y:event.pageY,Line truncated
|
||||
@@ -20,6 +20,7 @@ export class BaseInterpreter {
|
||||
root: HTMLElement;
|
||||
handler: EventListener;
|
||||
resizeObserver: ResizeObserver;
|
||||
intersectionObserver: IntersectionObserver;
|
||||
|
||||
nodes: Node[];
|
||||
stack: Node[];
|
||||
@@ -55,7 +56,7 @@ export class BaseInterpreter {
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
|
||||
createObserver(element: HTMLElement) {
|
||||
createResizeObserver(element: HTMLElement) {
|
||||
// Lazily create the resize observer
|
||||
if (!this.resizeObserver) {
|
||||
this.resizeObserver = new ResizeObserver((entries) => {
|
||||
@@ -67,13 +68,48 @@ export class BaseInterpreter {
|
||||
this.resizeObserver.observe(element);
|
||||
}
|
||||
|
||||
removeObserver(element: HTMLElement) {
|
||||
removeResizeObserver(element: HTMLElement) {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.unobserve(element);
|
||||
}
|
||||
}
|
||||
|
||||
handleIntersectionEvent(entry: IntersectionObserverEntry) {
|
||||
const target = entry.target;
|
||||
|
||||
let event = new CustomEvent<IntersectionObserverEntry>("visible", {
|
||||
bubbles: false,
|
||||
detail: entry,
|
||||
});
|
||||
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
|
||||
createIntersectionObserver(element: HTMLElement) {
|
||||
/// Lazily create the intersection observer
|
||||
if (!this.intersectionObserver) {
|
||||
this.intersectionObserver = new IntersectionObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
this.handleIntersectionEvent(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.intersectionObserver.observe(element);
|
||||
}
|
||||
|
||||
removeIntersectionObserver(element: HTMLElement) {
|
||||
if (this.intersectionObserver) {
|
||||
this.intersectionObserver.unobserve(element);
|
||||
}
|
||||
}
|
||||
|
||||
createListener(event_name: string, element: HTMLElement, bubbles: boolean) {
|
||||
if (event_name == "resize") {
|
||||
this.createResizeObserver(element);
|
||||
} else if (event_name == "visible") {
|
||||
this.createIntersectionObserver(element);
|
||||
}
|
||||
|
||||
if (bubbles) {
|
||||
if (this.global[event_name] === undefined) {
|
||||
this.global[event_name] = { active: 1, callback: this.handler };
|
||||
@@ -88,14 +124,14 @@ export class BaseInterpreter {
|
||||
}
|
||||
element.addEventListener(event_name, this.handler);
|
||||
}
|
||||
|
||||
if (event_name == "resize") {
|
||||
this.createObserver(element);
|
||||
}
|
||||
}
|
||||
|
||||
removeListener(element: HTMLElement, event_name: string, bubbles: boolean) {
|
||||
if (bubbles) {
|
||||
if (event_name == "resize") {
|
||||
this.removeResizeObserver(element);
|
||||
} else if (event_name == "visible") {
|
||||
this.removeIntersectionObserver(element);
|
||||
} else if (bubbles) {
|
||||
this.removeBubblingListener(event_name);
|
||||
} else {
|
||||
this.removeNonBubblingListener(element, event_name);
|
||||
|
||||
@@ -62,6 +62,8 @@ export function serializeEvent(
|
||||
const detail = event.detail;
|
||||
if (detail instanceof ResizeObserverEntry) {
|
||||
extend(serializeResizeEventDetail(detail));
|
||||
} else if (detail instanceof IntersectionObserverEntry) {
|
||||
extend(serializeIntersectionEventDetail(detail));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,21 +131,36 @@ export function serializeResizeEventDetail(
|
||||
border_box_size:
|
||||
detail.borderBoxSize !== undefined
|
||||
? toSerializableResizeObserverSize(
|
||||
detail.borderBoxSize[0],
|
||||
is_inline_width
|
||||
)
|
||||
detail.borderBoxSize[0],
|
||||
is_inline_width
|
||||
)
|
||||
: detail.contentRect,
|
||||
content_box_size:
|
||||
detail.contentBoxSize !== undefined
|
||||
? toSerializableResizeObserverSize(
|
||||
detail.contentBoxSize[0],
|
||||
is_inline_width
|
||||
)
|
||||
detail.contentBoxSize[0],
|
||||
is_inline_width
|
||||
)
|
||||
: detail.contentRect,
|
||||
content_rect: detail.contentRect,
|
||||
};
|
||||
}
|
||||
|
||||
export function serializeIntersectionEventDetail(
|
||||
detail: IntersectionObserverEntry): SerializedEvent {
|
||||
return {
|
||||
bounding_client_rect: detail.boundingClientRect,
|
||||
intersection_ratio: detail.intersectionRatio,
|
||||
intersection_rect: detail.intersectionRect,
|
||||
is_intersecting: detail.isIntersecting,
|
||||
root_bounds: detail.rootBounds,
|
||||
|
||||
// math.floor removes the floating point extra such that we serialize into integer
|
||||
// this *might* become an issue longer term
|
||||
time_ms: Math.floor(Date.now() + detail.time),
|
||||
}
|
||||
}
|
||||
|
||||
function serializeInputEvent(
|
||||
event: InputEvent,
|
||||
target: EventTarget
|
||||
|
||||
@@ -147,6 +147,14 @@ impl HtmlEventConverter for SerializedHtmlEventConverter {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn convert_visible_data(&self, event: &PlatformEventData) -> VisibleData {
|
||||
event
|
||||
.downcast::<SerializedVisibleData>()
|
||||
.cloned()
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn convert_wheel_data(&self, event: &PlatformEventData) -> WheelData {
|
||||
event
|
||||
.downcast::<SerializedWheelData>()
|
||||
|
||||
@@ -152,7 +152,7 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
|
||||
#[serde(tag = "method", content = "params")]
|
||||
enum IpcMessage {
|
||||
#[serde(rename = "user_event")]
|
||||
Event(HtmlEvent),
|
||||
Event(Box<HtmlEvent>),
|
||||
#[serde(rename = "query")]
|
||||
Query(QueryResult),
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ features = [
|
||||
"HtmlInputElement",
|
||||
"HtmlSelectElement",
|
||||
"HtmlTextAreaElement",
|
||||
"IntersectionObserverEntry",
|
||||
"InputEvent",
|
||||
"KeyboardEvent",
|
||||
"MouseEvent",
|
||||
|
||||
@@ -25,6 +25,7 @@ mod selection;
|
||||
mod toggle;
|
||||
mod touch;
|
||||
mod transition;
|
||||
mod visible;
|
||||
mod wheel;
|
||||
|
||||
/// A wrapper for the websys event that allows us to give it the impls from dioxus-html
|
||||
@@ -190,6 +191,15 @@ impl HtmlEventConverter for WebEventConverter {
|
||||
Synthetic::<web_sys::TransitionEvent>::from(downcast_event(event).raw.clone()).into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_visible_data(
|
||||
&self,
|
||||
event: &dioxus_html::PlatformEventData,
|
||||
) -> dioxus_html::VisibleData {
|
||||
Synthetic::<web_sys::IntersectionObserverEntry>::from(downcast_event(event).raw.clone())
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_wheel_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::WheelData {
|
||||
Synthetic::<web_sys::WheelEvent>::from(downcast_event(event).raw.clone()).into()
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
use dioxus_html::{
|
||||
geometry::{
|
||||
euclid::{Point2D, Size2D},
|
||||
PixelsRect,
|
||||
},
|
||||
HasVisibleData, VisibleData, VisibleError, VisibleResult,
|
||||
};
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{CustomEvent, DomRectReadOnly, Event, IntersectionObserverEntry};
|
||||
|
||||
use super::{Synthetic, WebEventExt};
|
||||
|
||||
impl From<Event> for Synthetic<IntersectionObserverEntry> {
|
||||
#[inline]
|
||||
fn from(e: Event) -> Self {
|
||||
<Synthetic<IntersectionObserverEntry> as From<&Event>>::from(&e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Event> for Synthetic<IntersectionObserverEntry> {
|
||||
#[inline]
|
||||
fn from(e: &Event) -> Self {
|
||||
let e: &CustomEvent = e.unchecked_ref();
|
||||
let value = e.detail();
|
||||
Self::new(value.unchecked_into::<IntersectionObserverEntry>())
|
||||
}
|
||||
}
|
||||
fn dom_rect_ro_to_pixel_rect(dom_rect: &DomRectReadOnly) -> PixelsRect {
|
||||
PixelsRect::new(
|
||||
Point2D::new(dom_rect.x(), dom_rect.y()),
|
||||
Size2D::new(dom_rect.width(), dom_rect.height()),
|
||||
)
|
||||
}
|
||||
|
||||
impl HasVisibleData for Synthetic<IntersectionObserverEntry> {
|
||||
/// Get the bounds rectangle of the target element
|
||||
fn get_bounding_client_rect(&self) -> VisibleResult<PixelsRect> {
|
||||
Ok(dom_rect_ro_to_pixel_rect(
|
||||
&self.event.bounding_client_rect(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Get the ratio of the intersectionRect to the boundingClientRect
|
||||
fn get_intersection_ratio(&self) -> VisibleResult<f64> {
|
||||
Ok(self.event.intersection_ratio())
|
||||
}
|
||||
|
||||
/// Get the rect representing the target's visible area
|
||||
fn get_intersection_rect(&self) -> VisibleResult<PixelsRect> {
|
||||
Ok(dom_rect_ro_to_pixel_rect(&self.event.intersection_rect()))
|
||||
}
|
||||
|
||||
/// Get if the target element intersects with the intersection observer's root
|
||||
fn is_intersecting(&self) -> VisibleResult<bool> {
|
||||
Ok(self.event.is_intersecting())
|
||||
}
|
||||
|
||||
/// Get the rect for the intersection observer's root
|
||||
fn get_root_bounds(&self) -> VisibleResult<PixelsRect> {
|
||||
match self.event.root_bounds() {
|
||||
Some(root_bounds) => Ok(dom_rect_ro_to_pixel_rect(&root_bounds)),
|
||||
None => Err(VisibleError::NotSupported),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a timestamp indicating the time at which the intersection was recorded
|
||||
fn get_time(&self) -> VisibleResult<SystemTime> {
|
||||
let ms_since_epoch = self.event.time();
|
||||
let rounded = ms_since_epoch.round() as u64;
|
||||
let duration = std::time::Duration::from_millis(rounded);
|
||||
Ok(SystemTime::UNIX_EPOCH + duration)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt for VisibleData {
|
||||
type WebEvent = IntersectionObserverEntry;
|
||||
|
||||
#[inline(always)]
|
||||
fn try_as_web_event(&self) -> Option<IntersectionObserverEntry> {
|
||||
self.downcast::<CustomEvent>()
|
||||
.and_then(|e| e.detail().dyn_into::<IntersectionObserverEntry>().ok())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user