Skip to content

Commit

Permalink
0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesbirtles committed Dec 12, 2020
0 parents commit 5596b31
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
Cargo.lock
20 changes: 20 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "yew-component-size"
version = "0.1.0"
authors = ["James Birtles <[email protected]>"]
edition = "2018"
description = "A Yew component that emits events when the parent component changes width/height."
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/AircastDev/yew-component-size"
keywords = ["yew", "component"]
categories = ["wasm"]

[features]
default = []

[dependencies]
yew = "0.17"
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["Element", "DomRect", "HtmlIFrameElement", "Window"] }
serde = { version = "1.0", optional = true, features = ["derive"] }
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# yew-component-size

![Crates.io](https://img.shields.io/crates/l/yew-component-size) ![Crates.io](https://img.shields.io/crates/v/yew-component-size)

A Yew component that emits events when the parent component changes width/height.
Only compatible with Yew using web_sys.

## Example:

```rust
let onsize = self.link.callback(|size: ComponentSize| {
// Access to `size.width` and `size.height`
});

html! {
// Parent that you're tracking the size of must be `position: relative`
<div style="position: relative;">
// ...
<ComponentSizeObserver onsize=onsize />
</div>
}
```

## How it works

This uses a trick borrowed from Svelte where we use an iframe that is positioned absolutely
to fill it's parent element, and then we listen to the resize event of iframe's window.

_**Note:** This incurs a small cost and so should not be used on a large number of elements at the same time._

## License

Licensed under either of

- Apache License, Version 2.0
([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license
([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)

at your option.

## Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
52 changes: 52 additions & 0 deletions examples/basic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use wasm_bindgen::prelude::*;
use yew::prelude::*;
use yew_component_size::{ComponentSize, ComponentSizeObserver};

struct MyComponent {
link: yew::ComponentLink<Self>,
size: ComponentSize,
}

enum Msg {
OnComponentSize(ComponentSize),
}

impl Component for MyComponent {
type Message = Msg;
type Properties = ();

fn create(_props: Self::Properties, link: yew::ComponentLink<Self>) -> Self {
Self {
link,
size: Default::default(),
}
}

fn update(&mut self, msg: Self::Message) -> yew::ShouldRender {
match msg {
Msg::OnComponentSize(size) => {
self.size = size;
true
}
}
}

fn change(&mut self, _props: Self::Properties) -> yew::ShouldRender {
false
}

fn view(&self) -> yew::Html {
let onsize = self.link.callback(Msg::OnComponentSize);
html! {
<div style="position:relative">
<span>{format!("width: {}px, height: {}px", self.size.width, self.size.height)}</span>
<ComponentSizeObserver onsize=onsize />
</div>
}
}
}

#[wasm_bindgen]
pub fn start_app() {
App::<MyComponent>::new().mount_to_body();
}
140 changes: 140 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#![deny(missing_docs)]

//! ![Crates.io](https://img.shields.io/crates/l/yew-component-size) ![Crates.io](https://img.shields.io/crates/v/yew-component-size)
//!
//! A Yew component that emits events when the parent component changes width/height.
//! Only compatible with Yew using web_sys.
//!
//! # Example:
//! ```rust
//! let onsize = self.link.callback(|size: ComponentSize| {
//! // Access to `size.width` and `size.height`
//! });
//!
//! html! {
//! // Parent that you're tracking the size of must be `position: relative`
//! <div style="position: relative;">
//! // ...
//! <ComponentSizeObserver onsize=onsize />
//! </div>
//! }
//! ```
//!
//! # How it works
//!
//! This uses a trick borrowed from Svelte where we use an iframe that is positioned absolutely
//! to fill it's parent element, and then we listen to the resize event of iframe's window.
//!
//! _**Note:** This incurs a small cost and so should not be used on a large number of elements at the same time._
//!
//! # License
//!
//! Licensed under either of
//!
//! * Apache License, Version 2.0
//! ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
//! * MIT license
//! ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
//!
//! at your option.
//!
//! # Contribution
//!
//! Unless you explicitly state otherwise, any contribution intentionally submitted
//! for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
//! dual licensed as above, without any additional terms or conditions.

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use wasm_bindgen::{prelude::Closure, JsCast};
use web_sys::HtmlIFrameElement;
use yew::{html, Callback, Component, NodeRef, Properties};

const IFRAME_STYLE: &str = "display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; border: 0; opacity: 0; pointer-events: none; z-index: -1;";

/// Yew component to observe changes to the size of the parent element.
///
/// See the crate documentation for an example and more information.
#[derive(Debug)]
pub struct ComponentSizeObserver {
props: Props,
iframe_ref: NodeRef,
on_resize: Option<Closure<dyn Fn()>>,
}

/// ComponentSizeObserver properties
#[derive(Properties, Clone, PartialEq, Debug)]
pub struct Props {
/// A callback that is fired when the component size changes for any reason.
pub onsize: Callback<ComponentSize>,
}

/// A struct containing the width and height of the component
#[derive(Default, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ComponentSize {
/// Width of the component in pixels
pub width: f64,

/// Height of the component in pixels
pub height: f64,
}

impl Component for ComponentSizeObserver {
type Message = ();
type Properties = Props;

fn create(props: Self::Properties, _link: yew::ComponentLink<Self>) -> Self {
Self {
props,
iframe_ref: Default::default(),
on_resize: None,
}
}

fn update(&mut self, _msg: Self::Message) -> yew::ShouldRender {
false
}

fn change(&mut self, props: Self::Properties) -> yew::ShouldRender {
if self.props != props {
self.props = props;
self.add_resize_listener();
false
} else {
false
}
}

fn view(&self) -> yew::Html {
html! {
<iframe style=IFRAME_STYLE ref=self.iframe_ref.clone() />
}
}

fn rendered(&mut self, first_render: bool) {
if first_render {
self.add_resize_listener();
}
}
}

impl ComponentSizeObserver {
fn add_resize_listener(&mut self) {
let iframe = self.iframe_ref.cast::<HtmlIFrameElement>().unwrap();
let window = iframe.content_window().unwrap();

let iframe_ref = self.iframe_ref.clone();
let size_callback = self.props.onsize.clone();
let on_resize = Closure::wrap(Box::new(move || {
let iframe = iframe_ref.cast::<HtmlIFrameElement>().unwrap();
let bcr = iframe.get_bounding_client_rect();
size_callback.emit(ComponentSize {
width: bcr.width(),
height: bcr.height(),
});
}) as Box<dyn Fn()>);
window.set_onresize(Some(on_resize.as_ref().unchecked_ref()));
self.on_resize = Some(on_resize);
}
}

0 comments on commit 5596b31

Please sign in to comment.