Browse Source

Merge remote-tracking branch 'upstream/master' into pr/Demonthos/473

Demonthos 3 years ago
parent
commit
55e262b2c6
64 changed files with 1123 additions and 765 deletions
  1. 0 1
      README.md
  2. 2 8
      docs/guide/src/SUMMARY.md
  3. 0 1
      docs/guide/src/advanced-guides/custom-renderer.md
  4. 0 2
      docs/guide/src/async/fetching.md
  5. 0 1
      docs/guide/src/async/sockets.md
  6. 0 1
      docs/guide/src/state/channels.md
  7. 0 1
      docs/guide/src/tutorial/advanced_guides.md
  8. 0 3
      docs/guide/src/tutorial/components.md
  9. 0 3
      docs/guide/src/tutorial/state.md
  10. 0 4
      docs/guide/src/tutorial/structure.md
  11. 0 4
      docs/guide/src/tutorial/styling.md
  12. 1 31
      docs/reference/src/SUMMARY.md
  13. 0 1
      docs/reference/src/depth/components.md
  14. 0 1
      docs/reference/src/depth/memoization.md
  15. 0 1
      docs/reference/src/depth/performance.md
  16. 0 1
      docs/reference/src/depth/props.md
  17. 0 1
      docs/reference/src/depth/rsx.md
  18. 0 1
      docs/reference/src/depth/testing.md
  19. 0 1
      docs/reference/src/depth/topics.md
  20. 0 1
      docs/reference/src/guide/bundline.md
  21. 0 1
      docs/reference/src/guide/components.md
  22. 0 1
      docs/reference/src/guide/custom_elements.md
  23. 0 1
      docs/reference/src/guide/custom_renderer.md
  24. 0 1
      docs/reference/src/guide/memoization.md
  25. 0 1
      docs/reference/src/guide/performance.md
  26. 0 1
      docs/reference/src/guide/props.md
  27. 0 1
      docs/reference/src/guide/rsx.md
  28. 0 1
      docs/reference/src/guide/server_side_components.md
  29. 0 1
      docs/reference/src/guide/testing.md
  30. 162 175
      examples/README.md
  31. 85 0
      examples/all_events.rs
  32. 28 22
      examples/calculator.rs
  33. 0 26
      examples/events.rs
  34. 0 56
      examples/mouse_event.rs
  35. 0 28
      examples/order.rs
  36. 21 16
      examples/pattern_model.rs
  37. 4 3
      examples/todomvc.rs
  38. 85 0
      examples/tui_all_events.rs
  39. 1 1
      examples/tui_border.rs
  40. 3 2
      examples/tui_buttons.rs
  41. 4 4
      examples/tui_hover.rs
  42. 18 12
      examples/tui_keys.rs
  43. 1 1
      examples/tui_text.rs
  44. 1 4
      packages/core/src/scopes.rs
  45. 2 0
      packages/desktop/src/controller.rs
  46. 1 1
      packages/hooks/src/use_shared_state.rs
  47. 1 4
      packages/hooks/src/useref.rs
  48. 6 6
      packages/hooks/src/usestate.rs
  49. 1 1
      packages/html/Cargo.toml
  50. 195 44
      packages/html/src/events.rs
  51. 71 0
      packages/html/src/geometry.rs
  52. 23 0
      packages/html/src/input_data.rs
  53. 30 21
      packages/html/src/web_sys_bind/events.rs
  54. 45 44
      packages/interpreter/src/interpreter.js
  55. 0 1
      packages/liveview/src/interpreter.js
  56. 3 3
      packages/router/tests/web_router.rs
  57. 2 2
      packages/rsx_interpreter/src/captuered_context.rs
  58. 17 0
      packages/rsx_interpreter/src/error.rs
  59. 12 4
      packages/rsx_interpreter/src/interperter.rs
  60. 2 0
      packages/rsx_interpreter/src/lib.rs
  61. 241 158
      packages/tui/src/hooks.rs
  62. 3 2
      packages/tui/tests/events.rs
  63. 52 46
      packages/web/src/lib.rs
  64. 0 1
      packages/web/src/olddom.rs

+ 0 - 1
README.md

@@ -86,7 +86,6 @@ If you know React, then you already know Dioxus.
         <th><a href="https://dioxuslabs.com/reference/desktop/">Desktop</a></th>
         <th><a href="https://dioxuslabs.com/reference/desktop/">Desktop</a></th>
         <th><a href="https://dioxuslabs.com/reference/ssr/">SSR</a></th>
         <th><a href="https://dioxuslabs.com/reference/ssr/">SSR</a></th>
         <th><a href="https://dioxuslabs.com/reference/mobile/">Mobile</a></th>
         <th><a href="https://dioxuslabs.com/reference/mobile/">Mobile</a></th>
-        <th><a href="https://dioxuslabs.com/guide/concepts/managing_state.html">State</a></th>
     <tr>
     <tr>
 </table>
 </table>
 
 

+ 2 - 8
docs/guide/src/SUMMARY.md

@@ -32,18 +32,12 @@
 - [Working with Async](async/index.md)
 - [Working with Async](async/index.md)
   - [UseFuture](async/use_future.md)
   - [UseFuture](async/use_future.md)
   - [UseCoroutine](async/coroutines.md)
   - [UseCoroutine](async/coroutines.md)
-  <!-- - [Fetching](async/fetching.md) -->
-  <!-- - [Updating State](async/loading_state.md)
-  - [WebSockets](async/sockets.md) -->
-  <!-- - [Tasks](async/asynctasks.md) -->
+  - [Updating State](async/loading_state.md)
+  - [Tasks](async/asynctasks.md)
 
 
 <!--
 <!--
 - [Putting it all together: Dog Search Engine](tutorial/index.md)
 - [Putting it all together: Dog Search Engine](tutorial/index.md)
   - [New app](tutorial/new_app.md)
   - [New app](tutorial/new_app.md)
-  - [Structuring our app](tutorial/structure.md)
-  - [Defining State](tutorial/state.md)
-  - [Defining Components](tutorial/components.md)
-  - [Styling](tutorial/styling.md)
   - [Bundling](tutorial/publishing.md) -->
   - [Bundling](tutorial/publishing.md) -->
 
 
 
 

+ 0 - 1
docs/guide/src/advanced-guides/custom-renderer.md

@@ -204,7 +204,6 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
                 fn ctrl_key(&self) -> bool { self.0.ctrl_key() }
                 fn ctrl_key(&self) -> bool { self.0.ctrl_key() }
                 fn key(&self) -> String { self.0.key() }
                 fn key(&self) -> String { self.0.key() }
                 fn key_code(&self) -> usize { self.0.key_code() }
                 fn key_code(&self) -> usize { self.0.key_code() }
-                fn locale(&self) -> String { self.0.locale() }
                 fn location(&self) -> usize { self.0.location() }
                 fn location(&self) -> usize { self.0.location() }
                 fn meta_key(&self) -> bool { self.0.meta_key() }
                 fn meta_key(&self) -> bool { self.0.meta_key() }
                 fn repeat(&self) -> bool { self.0.repeat() }
                 fn repeat(&self) -> bool { self.0.repeat() }

+ 0 - 2
docs/guide/src/async/fetching.md

@@ -1,2 +0,0 @@
-# Fetching
-

+ 0 - 1
docs/guide/src/async/sockets.md

@@ -1 +0,0 @@
-# WebSockets

+ 0 - 1
docs/guide/src/state/channels.md

@@ -1 +0,0 @@
-# Channels

+ 0 - 1
docs/guide/src/tutorial/advanced_guides.md

@@ -1 +0,0 @@
-# Advanced Guides

+ 0 - 3
docs/guide/src/tutorial/components.md

@@ -1,3 +0,0 @@
-# Defining Components
-
-This section is currently under construction! 🏗

+ 0 - 3
docs/guide/src/tutorial/state.md

@@ -1,3 +0,0 @@
-# Defining State
-
-This section is currently under construction! 🏗

+ 0 - 4
docs/guide/src/tutorial/structure.md

@@ -1,4 +0,0 @@
-# Structuring our app
-
-
-This section is currently under construction! 🏗

+ 0 - 4
docs/guide/src/tutorial/styling.md

@@ -1,4 +0,0 @@
-# Styling
-
-
-This section is currently under construction! 🏗

+ 1 - 31
docs/reference/src/SUMMARY.md

@@ -11,35 +11,5 @@
   
   
 - [Advanced Guides](guide/index.md)
 - [Advanced Guides](guide/index.md)
   - [RSX in Depth](guide/rsx_in_depth.md)
   - [RSX in Depth](guide/rsx_in_depth.md)
-  - [Components](guide/components.md)
-  - [Props](guide/props.md)
-  - [Memoization](guide/memoization.md)
-  - [Performance](guide/performance.md)
-  - [Testing](guide/testing.md)
-  - [Building Elements with NodeFactory](guide/rsx.md)
-  - [Custom Elements](guide/custom_elements.md)
   - [Custom Renderer](guide/custom_renderer.md)
   - [Custom Renderer](guide/custom_renderer.md)
-  - [Server-side components](guide/server_side_components.md)
-  - [Bundling and Distributing](guide/bundline.md)
-  - [Hot Reloading Rsx](guide/hot_reloading.md)
-
-- [Reference Guide](reference/reference.md)
-  - [Anti-patterns](reference/anti.md)
-  - [Children](reference/children.md)
-  - [Conditional Rendering](reference/conditional.md)
-  - [Controlled Inputs](reference/controlled.md)
-  - [Custom Elements](reference/custom.md)
-  - [Empty Components](reference/empty.md)
-  - [Error Handling](reference/error.md)
-  - [Fragments](reference/fragments.md)
-  - [Global CSS](reference/global.md)
-  - [Inline Styles](reference/inline.md)
-  - [Iterators](reference/iterators.md)
-  - [Listeners](reference/listeners.md)
-  - [Memoization](reference/memoization.md)
-  - [Node Refs](reference/node.md)
-  - [Spread Pattern](reference/spread.md)
-  - [State Management](reference/state.md)
-  - [Suspense](reference/suspense.md)
-  - [task](reference/task.md)
-  - [Testing](reference/testing.md)
+  - [Hot Reloading Rsx](guide/hot_reloading.md)

+ 0 - 1
docs/reference/src/depth/components.md

@@ -1 +0,0 @@
-# Components

+ 0 - 1
docs/reference/src/depth/memoization.md

@@ -1 +0,0 @@
-# Memoization

+ 0 - 1
docs/reference/src/depth/performance.md

@@ -1 +0,0 @@
-# Performance

+ 0 - 1
docs/reference/src/depth/props.md

@@ -1 +0,0 @@
-# Props

+ 0 - 1
docs/reference/src/depth/rsx.md

@@ -1 +0,0 @@
-# RSX

+ 0 - 1
docs/reference/src/depth/testing.md

@@ -1 +0,0 @@
-# Testing

+ 0 - 1
docs/reference/src/depth/topics.md

@@ -1 +0,0 @@
-# Topics in Depth

+ 0 - 1
docs/reference/src/guide/bundline.md

@@ -1 +0,0 @@
-# Bundling and Distributing

+ 0 - 1
docs/reference/src/guide/components.md

@@ -1 +0,0 @@
-# Components

+ 0 - 1
docs/reference/src/guide/custom_elements.md

@@ -1 +0,0 @@
-# Custom Elements

+ 0 - 1
docs/reference/src/guide/custom_renderer.md

@@ -206,7 +206,6 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
                     ctrl_key: event.ctrl_key(),
                     ctrl_key: event.ctrl_key(),
                     meta_key: event.meta_key(),
                     meta_key: event.meta_key(),
                     shift_key: event.shift_key(),
                     shift_key: event.shift_key(),
-                    locale: "".to_string(),
                     location: event.location(),
                     location: event.location(),
                     repeat: event.repeat(),
                     repeat: event.repeat(),
                     which: event.which(),
                     which: event.which(),

+ 0 - 1
docs/reference/src/guide/memoization.md

@@ -1 +0,0 @@
-# Memoization

+ 0 - 1
docs/reference/src/guide/performance.md

@@ -1 +0,0 @@
-# Performance

+ 0 - 1
docs/reference/src/guide/props.md

@@ -1 +0,0 @@
-# Props

+ 0 - 1
docs/reference/src/guide/rsx.md

@@ -1 +0,0 @@
-# Building Elements with NodeFactory

+ 0 - 1
docs/reference/src/guide/server_side_components.md

@@ -1 +0,0 @@
-# Server-side components

+ 0 - 1
docs/reference/src/guide/testing.md

@@ -1 +0,0 @@
-# Testing

+ 162 - 175
examples/README.md

@@ -1,189 +1,176 @@
 # Examples
 # Examples
 
 
-Most of these examples are run through webview so you don't need the Dioxus CLI installed to preview the functionality.
-
-These examples are fully-fledged micro apps. They can be ran with the `cargo run --example XYZ`
-
-| Example                                             | What it does                                | Status |
-| --------------------------------------------------- | ------------------------------------------- | ------ |
-| [The basics](./basics.rs)                           | A few basic examples to preview Dioxus      | 🛠      |
-| [fine grained reactivity](./signals.rs)             | Escape `diffing` by writing values directly | 🛠      |
-| [Global State Management](./statemanagement.rs)     | Share state between components              | 🛠      |
-| [Virtual Refs]()                                    | Cross-platform imperative elements          | 🛠      |
-| [Inline Styles](./inline-styles.rs)                 | Define styles for elements inline           | 🛠      |
-| [Conditional Rendering](./conditional-rendering.rs) | Hide/Show elements using conditionals       | ✅      |
-
-These examples are not necessarily meant to be run, but rather serve as a reference for the given functionality.
-
-| Example                                             | What it does                                    | Status |
-| --------------------------------------------------- | ----------------------------------------------- | ------ |
-| [The basics](./basics.rs)                           | A few basic examples to preview Dioxus          | 🛠      |
-| [fine grained reactivity](./signals.rs)             | Escape `diffing` by writing values directly     | 🛠      |
-| [Global State Management](./statemanagement.rs)     | Share state between components                  | 🛠      |
-| [Virtual Refs]()                                    | Cross-platform imperative elements              | 🛠      |
-| [Inline Styles](./inline-styles.rs)                 | Define styles for elements inline               | 🛠      |
-| [Conditional Rendering](./conditional-rendering.rs) | Hide/Show elements using conditionals           | ✅      |
-| [Maps/Iterators](./iterators.rs)                    | Use iterators in the rsx! macro                 | ✅      |
-| [Render To string](./tostring.rs)                   | Render a mounted virtualdom to a string         | 🛠      |
-| [Component Children](./children.rs)                 | Pass children into child components             | 🛠      |
-| [Function Driven children]()                        | Pass functions to make VNodes                   | 🛠      |
-| [Memoization & Borrowed Data](./memo.rs)            | Suppress renders, borrow from parents           | ✅      |
-| [Fragments](./fragments.rs)                         | Support root-less element groups                | ✅      |
-| [Null/None Components](./empty.rs)                  | Return nothing!                                 | 🛠      |
-| [Spread Pattern for props](./spreadpattern.rs)      | Manually specify and override props             | ✅      |
-| [Controlled Inputs](./controlled-inputs.rs)         | this does                                       | 🛠      |
-| [Custom Elements]()                                 | Define custom elements                          | 🛠      |
-| [Web Components]()                                  | Custom elements to interface with WebComponents | 🛠      |
-| [Testing And debugging]()                           | this does                                       | 🛠      |
-| [Asynchronous Data]()                               | Using suspense to wait for data                 | 🛠      |
-| [Fiber/Scheduled Rendering]()                       | this does                                       | 🛠      |
-| [CSS Compiled Styles]()                             | this does                                       | 🛠      |
-| [Anti-patterns](./antipatterns.rs)                  | A collection of discouraged patterns            | ✅      |
-| [Complete rsx reference](./rsx_usage.rs)            | A complete reference for all rsx! usage         | ✅      |
-| [Event Listeners](./listener.rs)                    | Attach closures to events on elements           | ✅      |
-| [Inline Props](./inlineprops.rs)                    | Using the `#[inline_props]` macro               | ✅      |
-| [Eval](./eval.rs)                                   | Evaluate dynamic JavaScript code                | ✅      |
-
-
-## Show me some examples!
-
-In our collection of examples, guides, and tutorials, we have:
-- The book (an introductory course)
-- The guide (an in-depth analysis of everything in Dioxus)
-- The reference (a collection of examples with heavy documentation)
-- The general examples
-- The platform-specific examples (web, ssr, desktop, mobile, server)
-
-Here's what a few common tasks look like in Dioxus:
-
-Nested components with children and internal state:
-```rust
-fn App(cx: Scope) -> Element {
-  cx.render(rsx!( Toggle { "Toggle me" } ))
-}
-
-#[derive(PartialEq, Props)]
-struct ToggleProps { children: Element }
-
-fn Toggle(cx: Scope<ToggleProps>) -> Element {
-  let mut toggled = use_state(&cx, || false);
-  cx.render(rsx!{
-    div {
-      &cx.props.children
-      button { onclick: move |_| toggled.set(true),
-        toggled.and_then(|| "On").or_else(|| "Off")
-      }
-    }
-  })
-}
-```
+These examples are fully-fledged mini Dioxus apps.
 
 
-Controlled inputs:
-```rust
-fn App(cx: Scope) -> Element {
-  let value = use_state(&cx, String::new);
-  cx.render(rsx!(
-    input {
-      "type": "text",
-      value: "{value}",
-      oninput: move |evt| value.set(evt.value.clone())
-    }
-  ))
-}
-```
+You can run them with `cargo run --example EXAMPLE_NAME`. Example:
 
 
-Lists and Conditional rendering:
-```rust
-fn App(cx: Scope) -> Element {
-  let list = (0..10).map(|i| {
-    rsx!(li { key: "{i}", "Value: {i}" })
-  });
-
-  let title = match list.len() {
-    0 => rsx!("Not enough"),
-    _ => rsx!("Plenty!"),
-  };
-
-  if should_show {
-    cx.render(rsx!(
-      title,
-      ul { list }
-    ))
-  } else {
-    None
-  }
-}
+```shell
+cargo run --example hello_world
 ```
 ```
 
 
-Tiny components:
-```rust
-static App: Component = |cx| rsx!(cx, div {"hello world!"});
-```
+(Most of these examples are run through webview, so you don't need the Dioxus CLI installed)
 
 
-Borrowed prop contents:
-```rust
-fn App(cx: Scope) -> Element {
-  let name = use_state(&cx, || String::from("example"));
-  rsx!(cx, Child { title: name.as_str() })
-}
+## Basic Features
 
 
-#[derive(Props)]
-struct ChildProps<'a> { title: &'a str }
+[hello_world](./hello_world.rs) - Most basic example
 
 
-fn Child(cx: Scope<ChildProps>) -> Element {
-  rsx!(cx, "Hello {cx.props.title}")
-}
-```
+[readme](./readme.rs) - Counter example from the Readme
 
 
-Global State
-```rust
-struct GlobalState { name: String }
+[custom_assets](./custom_assets.rs) - Include images
 
 
-fn App(cx: Scope) -> Element {
-  use_provide_shared_state(cx, || GlobalState { name: String::from("Toby") })
-  rsx!(cx, Leaf {})
-}
+[custom_element](./custom_element.rs) - Render webcomponents
 
 
-fn Leaf(cx: Scope) -> Element {
-  let state = use_consume_shared_state::<GlobalState>(cx)?;
-  rsx!(cx, "Hello {state.name}")
-}
-```
+[custom_html](./custom_html.rs) - Customize wrapper HTML
 
 
-Router (inspired by Yew-Router)
-```rust
-#[derive(PartialEq, Clone,  Hash, Eq, Routable)]
-enum Route {
-  #[at("/")]
-  Home,
-  #[at("/post/{id}")]
-  Post(id)
-}
-
-fn App(cx: Scope) -> Element {
-  let route = use_router(cx, Route::parse);
-  cx.render(rsx!(div {
-    match route {
-      Route::Home => rsx!( Home {} ),
-      Route::Post(id) => rsx!( Post { id: id })
-    }
-  }))
-}
-```
+[eval](./eval.rs) - Evaluate JS expressions
 
 
-Suspense
-```rust
-fn App(cx: Scope) -> Element {
-  let doggo = use_suspense(cx,
-    || async { reqwest::get("https://dog.ceo/api/breeds/image/random").await.unwrap().json::<Response>().await.unwrap() },
-    |response| cx.render(rsx!( img { src: "{response.message}" }))
-  );
-
-  cx.render(rsx!{
-    div {
-      "One doggo coming right up:",
-      doggo
-    }
-  })
-}
-```
+### RSX
+
+[rsx_usage](./rsx_usage.rs) - Demo of all RSX features
+
+[xss_safety](./xss_safety.rs) - You can include text without worrying about injections by default
+
+### Props
+
+[borrowed](./borrowed.rs) - Borrowed props
+
+[inlineprops](./inlineprops.rs) - Demo of `inline_props` macro
+
+[optional_props](./optional_props.rs) - Optional props
+
+### CSS
+
+[all_css](./all_css.rs) - You can specify any CSS attribute
+
+[tailwind](./tailwind.rs) - You can use a library for styling
+
+## Input Handling
+
+[all_events](./all_events.rs) - Basic event handling demo
+
+[filedragdrop](./filedragdrop.rs) - Handle dropped files
+
+[form](./form.rs) - Handle form submission
+
+[inputs](./inputs.rs) - Input values
+
+[nested_listeners](./nested_listeners.rs) - Nested handlers and bubbling
+
+[textarea](textarea.rs) - Text area input
+
+### State Management
+
+[fermi](./fermi.rs) - Fermi library for state management
+
+[pattern_reducer](./pattern_reducer.rs) - The reducer pattern with `use_state`
+
+[rsx_compile_fail](./rsx_compile_fail.rs)
+
+### Async
+
+[login_form](./login_form.rs) - Login endpoint example
+
+[suspense](./suspense.rs) - Render placeholders while data is loading
+
+[tasks](./tasks.rs) - Continuously run future
+
+### SVG
+
+[svg_basic](./svg_basic.rs)
+
+[svg](./svg.rs)
+
+### Performance
+
+[framework_benchmark](./framework_benchmark.rs) - Renders a huge list
+
+> Note: The benchmark should be run in release mode:
+>
+>```shell
+> cargo run --example framework_benchmark --release
+>```
+
+[heavy_compute](./heavy_compute.rs) - How to deal with expensive operations
+
+## Server-side rendering
+
+[ssr](./ssr.rs) - Rendering RSX server-side
+
+[hydration](./hydration.rs) - Pre-rendering with hydration
+
+## Common Patterns
+
+[disabled](./disabled.rs) - Disable buttons conditionally
+
+[error_handle](./error_handle.rs) - Handle errors with early return
+
+## Routing
+
+[flat_router](./flat_router.rs) - Basic, flat route example
+
+[router](./router.rs) - Router example
+
+[link](./link.rs) - Internal, external and custom links
+
+## Platform Features
+
+[window_event](./window_event.rs) - Window decorations, fullscreen, minimization, etc.
+
+[window_zoom](./window_zoom.rs) – Zoom in or out
+
+## Example Apps
+
+[calculator](./calculator.rs) - Simple calculator
+
+[pattern_model](./pattern_model.rs) - Simple calculator, but using a custom struct as the model
+
+[crm](./crm.rs) - Toy multi-page customer management app
+
+[dog_app](./dog_app.rs) - Accesses dog API
+
+[file_explorer](./file_explorer.rs) - File browser that uses `use_ref` to interact with the model
+
+[todomvc](./todomvc.rs) - Todo task list example
+
+## Terminal UI
+
+[tui_border](./tui_border.rs)
+
+[tui_buttons](./tui_buttons.rs)
+
+[tui_color_test](./tui_color_test.rs)
+
+[tui_components](./tui_components.rs)
+
+[tui_frame](./tui_frame.rs)
+
+[tui_hover](./tui_hover.rs)
+
+[tui_keys](./tui_keys.rs)
+
+[tui_list](./tui_list.rs)
+
+[tui_margin](./tui_margin.rs)
+
+[tui_quadrants](./tui_quadrants.rs)
+
+[tui_readme](./tui_readme.rs)
+
+[tui_task](./tui_task.rs)
+
+[tui_text](./tui_text.rs)
+
+# TODO
+Missing Features
+- Fine-grained reactivity
+- Refs - imperative handles to elements
+- Function-driven children: Pass functions to make VNodes
+
+Missing examples
+- Shared state
+- Root-less element groups
+- Spread props
+- Custom elements
+- Component Children: Pass children into child components
+- Render To string: Render a mounted virtualdom to a string
+- Testing and Debugging

+ 85 - 0
examples/all_events.rs

@@ -0,0 +1,85 @@
+use dioxus::prelude::*;
+use dioxus_html::on::{FocusData, KeyboardData, MouseData, WheelData};
+use std::sync::Arc;
+
+fn main() {
+    dioxus::desktop::launch(app);
+}
+
+#[derive(Debug)]
+enum Event {
+    MouseMove(Arc<MouseData>),
+    MouseClick(Arc<MouseData>),
+    MouseDoubleClick(Arc<MouseData>),
+    MouseDown(Arc<MouseData>),
+    MouseUp(Arc<MouseData>),
+
+    Wheel(Arc<WheelData>),
+
+    KeyDown(Arc<KeyboardData>),
+    KeyUp(Arc<KeyboardData>),
+    KeyPress(Arc<KeyboardData>),
+
+    FocusIn(Arc<FocusData>),
+    FocusOut(Arc<FocusData>),
+}
+
+const MAX_EVENTS: usize = 8;
+
+fn app(cx: Scope) -> Element {
+    let container_style = r#"
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+    "#;
+    let rect_style = r#"
+        background: deepskyblue;
+        height: 50vh;
+        width: 50vw;
+        color: white;
+        padding: 20px;
+        margin: 20px;
+        text-aligh: center;
+    "#;
+
+    let events = use_ref(&cx, || Vec::new());
+
+    let events_lock = events.read();
+    let first_index = events_lock.len().saturating_sub(MAX_EVENTS);
+    let events_rendered = events_lock[first_index..]
+        .iter()
+        .map(|event| cx.render(rsx!(div {"{event:?}"})));
+
+    let log_event = move |event: Event| {
+        events.write().push(event);
+    };
+
+    cx.render(rsx! (
+        div {
+            style: "{container_style}",
+            div {
+                style: "{rect_style}",
+                // focusing is necessary to catch keyboard events
+                tabindex: "0",
+
+                onmousemove: move |event| log_event(Event::MouseMove(event.data)),
+                onclick: move |event| log_event(Event::MouseClick(event.data)),
+                ondblclick: move |event| log_event(Event::MouseDoubleClick(event.data)),
+                onmousedown: move |event| log_event(Event::MouseDown(event.data)),
+                onmouseup: move |event| log_event(Event::MouseUp(event.data)),
+
+                onwheel: move |event| log_event(Event::Wheel(event.data)),
+
+                onkeydown: move |event| log_event(Event::KeyDown(event.data)),
+                onkeyup: move |event| log_event(Event::KeyUp(event.data)),
+                onkeypress: move |event| log_event(Event::KeyPress(event.data)),
+
+                onfocusin: move |event| log_event(Event::FocusIn(event.data)),
+                onfocusout: move |event| log_event(Event::FocusOut(event.data)),
+
+                "Hover, click, type or scroll to see the info down below"
+            }
+            div { events_rendered },
+        },
+    ))
+}

+ 28 - 22
examples/calculator.rs

@@ -5,6 +5,7 @@ This calculator version uses React-style state management. All state is held as
 
 
 use dioxus::events::*;
 use dioxus::events::*;
 use dioxus::prelude::*;
 use dioxus::prelude::*;
+use dioxus_html::input_data::keyboard_types::Key;
 
 
 fn main() {
 fn main() {
     use dioxus::desktop::tao::dpi::LogicalSize;
     use dioxus::desktop::tao::dpi::LogicalSize;
@@ -29,33 +30,38 @@ fn app(cx: Scope) -> Element {
 
 
     let input_operator = move |key: &str| val.make_mut().push_str(key);
     let input_operator = move |key: &str| val.make_mut().push_str(key);
 
 
+    let handle_key_down_event = move |evt: KeyboardEvent| match evt.key() {
+        Key::Backspace => {
+            if !val.len() != 0 {
+                val.make_mut().pop();
+            }
+        }
+        Key::Character(character) => match character.as_str() {
+            "+" => input_operator("+"),
+            "-" => input_operator("-"),
+            "/" => input_operator("/"),
+            "*" => input_operator("*"),
+            "0" => input_digit(0),
+            "1" => input_digit(1),
+            "2" => input_digit(2),
+            "3" => input_digit(3),
+            "4" => input_digit(4),
+            "5" => input_digit(5),
+            "6" => input_digit(6),
+            "7" => input_digit(7),
+            "8" => input_digit(8),
+            "9" => input_digit(9),
+            _ => {}
+        },
+        _ => {}
+    };
+
     cx.render(rsx!(
     cx.render(rsx!(
         style { [include_str!("./assets/calculator.css")] }
         style { [include_str!("./assets/calculator.css")] }
         div { id: "wrapper",
         div { id: "wrapper",
             div { class: "app",
             div { class: "app",
                 div { class: "calculator",
                 div { class: "calculator",
-                    onkeydown: move |evt| match evt.key_code {
-                        KeyCode::Add => input_operator("+"),
-                        KeyCode::Subtract => input_operator("-"),
-                        KeyCode::Divide => input_operator("/"),
-                        KeyCode::Multiply => input_operator("*"),
-                        KeyCode::Num0 => input_digit(0),
-                        KeyCode::Num1 => input_digit(1),
-                        KeyCode::Num2 => input_digit(2),
-                        KeyCode::Num3 => input_digit(3),
-                        KeyCode::Num4 => input_digit(4),
-                        KeyCode::Num5 => input_digit(5),
-                        KeyCode::Num6 => input_digit(6),
-                        KeyCode::Num7 => input_digit(7),
-                        KeyCode::Num8 => input_digit(8),
-                        KeyCode::Num9 => input_digit(9),
-                        KeyCode::Backspace => {
-                            if !val.len() != 0 {
-                                val.make_mut().pop();
-                            }
-                        }
-                        _ => {}
-                    },
+                    onkeydown: handle_key_down_event,
                     div { class: "calculator-display", [val.to_string()] }
                     div { class: "calculator-display", [val.to_string()] }
                     div { class: "calculator-keypad",
                     div { class: "calculator-keypad",
                         div { class: "input-keys",
                         div { class: "input-keys",

+ 0 - 26
examples/events.rs

@@ -1,26 +0,0 @@
-use dioxus::prelude::*;
-
-fn main() {
-    dioxus::desktop::launch(app);
-}
-
-fn app(cx: Scope) -> Element {
-    cx.render(rsx! {
-        div {
-            button {
-                ondblclick: move |_| {
-                    //
-                    println!("double clicked!");
-                },
-                "Click me!"
-            }
-            input {
-                 onfocusin: move |_| {
-                    //
-                    println!("blurred!");
-                },
-                "onblur": "console.log('blurred!')"
-            }
-        }
-    })
-}

+ 0 - 56
examples/mouse_event.rs

@@ -1,56 +0,0 @@
-use dioxus::prelude::*;
-use dioxus_core::UiEvent;
-use dioxus_html::on::MouseData;
-
-fn main() {
-    dioxus::desktop::launch(app);
-}
-
-fn app(cx: Scope) -> Element {
-    let page_coordinates = use_state(&cx, || "".to_string());
-    let screen_coordinates = use_state(&cx, || "".to_string());
-    let element_coordinates = use_state(&cx, || "".to_string());
-    let buttons = use_state(&cx, || "".to_string());
-    let modifiers = use_state(&cx, || "".to_string());
-
-    let container_style = r#"
-        display: flex;
-        flex-direction: column;
-        align-items: center;
-    "#;
-    let rect_style = r#"
-        background: deepskyblue;
-        height: 50vh;
-        width: 50vw;
-    "#;
-
-    let update_mouse_position = move |event: UiEvent<MouseData>| {
-        let mouse_data = event.data;
-
-        page_coordinates.set(format!("{:?}", mouse_data.page_coordinates()));
-        screen_coordinates.set(format!("{:?}", mouse_data.screen_coordinates()));
-        element_coordinates.set(format!("{:?}", mouse_data.element_coordinates()));
-
-        // Note: client coordinates are also available, but they would be the same as the page coordinates in this example, because there is no scrolling.
-
-        buttons.set(format!("{:?}", mouse_data.held_buttons()));
-        modifiers.set(format!("{:?}", mouse_data.modifiers()));
-    };
-
-    cx.render(rsx! (
-        div {
-            style: "{container_style}",
-            "Hover over to display coordinates:",
-            div {
-                style: "{rect_style}",
-                onmousemove: update_mouse_position,
-                prevent_default: "mousedown",
-            }
-            div {"Page coordinates: {page_coordinates}"},
-            div {"Screen coordinates: {screen_coordinates}"},
-            div {"Element coordinates: {element_coordinates}"},
-            div {"Buttons: {buttons}"},
-            div {"Modifiers: {modifiers}"},
-        }
-    ))
-}

+ 0 - 28
examples/order.rs

@@ -1,28 +0,0 @@
-#![allow(non_snake_case)]
-
-//! This example proves that instantly resolving futures don't cause issues
-
-use dioxus::prelude::*;
-
-fn main() {
-    dioxus::desktop::launch(App);
-}
-
-fn App(cx: Scope) -> Element {
-    cx.render(rsx!(Demo {}))
-}
-
-fn Demo(cx: Scope) -> Element {
-    let fut1 = use_future(&cx, (), |_| async move {
-        std::thread::sleep(std::time::Duration::from_millis(100));
-        10
-    });
-
-    cx.render(match fut1.value() {
-        Some(value) => {
-            let content = format!("content : {:?}", value);
-            rsx!(div{ "{content}" })
-        }
-        None => rsx!(div{"computing!"}),
-    })
-}

+ 21 - 16
examples/pattern_model.rs

@@ -20,6 +20,7 @@
 use dioxus::desktop::wry::application::dpi::LogicalSize;
 use dioxus::desktop::wry::application::dpi::LogicalSize;
 use dioxus::events::*;
 use dioxus::events::*;
 use dioxus::prelude::*;
 use dioxus::prelude::*;
+use dioxus_html::input_data::keyboard_types::Key;
 
 
 fn main() {
 fn main() {
     dioxus::desktop::launch_cfg(app, |cfg| {
     dioxus::desktop::launch_cfg(app, |cfg| {
@@ -212,22 +213,26 @@ impl Calculator {
         self.waiting_for_operand = true;
         self.waiting_for_operand = true;
     }
     }
     fn handle_keydown(&mut self, evt: KeyboardEvent) {
     fn handle_keydown(&mut self, evt: KeyboardEvent) {
-        match evt.key_code {
-            KeyCode::Backspace => self.backspace(),
-            KeyCode::Num0 => self.input_digit(0),
-            KeyCode::Num1 => self.input_digit(1),
-            KeyCode::Num2 => self.input_digit(2),
-            KeyCode::Num3 => self.input_digit(3),
-            KeyCode::Num4 => self.input_digit(4),
-            KeyCode::Num5 => self.input_digit(5),
-            KeyCode::Num6 => self.input_digit(6),
-            KeyCode::Num7 => self.input_digit(7),
-            KeyCode::Num8 => self.input_digit(8),
-            KeyCode::Num9 => self.input_digit(9),
-            KeyCode::Add => self.operator = Some(Operator::Add),
-            KeyCode::Subtract => self.operator = Some(Operator::Sub),
-            KeyCode::Divide => self.operator = Some(Operator::Div),
-            KeyCode::Multiply => self.operator = Some(Operator::Mul),
+        match evt.key() {
+            Key::Backspace => self.backspace(),
+            Key::Character(c) => match c.as_str() {
+                "0" => self.input_digit(0),
+                "1" => self.input_digit(1),
+                "2" => self.input_digit(2),
+                "3" => self.input_digit(3),
+                "4" => self.input_digit(4),
+                "5" => self.input_digit(5),
+                "6" => self.input_digit(6),
+                "7" => self.input_digit(7),
+                "8" => self.input_digit(8),
+                "9" => self.input_digit(9),
+                "+" => self.operator = Some(Operator::Add),
+                "-" => self.operator = Some(Operator::Sub),
+                "/" => self.operator = Some(Operator::Div),
+                "*" => self.operator = Some(Operator::Mul),
+                _ => {}
+            },
+
             _ => {}
             _ => {}
         }
         }
     }
     }

+ 4 - 3
examples/todomvc.rs

@@ -1,4 +1,5 @@
 use dioxus::prelude::*;
 use dioxus::prelude::*;
+use dioxus_elements::input_data::keyboard_types::Key;
 
 
 fn main() {
 fn main() {
     dioxus::desktop::launch(app);
     dioxus::desktop::launch(app);
@@ -56,7 +57,7 @@ pub fn app(cx: Scope<()>) -> Element {
                         autofocus: "true",
                         autofocus: "true",
                         oninput: move |evt| draft.set(evt.value.clone()),
                         oninput: move |evt| draft.set(evt.value.clone()),
                         onkeydown: move |evt| {
                         onkeydown: move |evt| {
-                            if evt.key == "Enter" && !draft.is_empty() {
+                            if evt.key() == Key::Enter && !draft.is_empty() {
                                 todos.make_mut().insert(
                                 todos.make_mut().insert(
                                     **todo_id,
                                     **todo_id,
                                     TodoItem {
                                     TodoItem {
@@ -148,8 +149,8 @@ pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
                     autofocus: "true",
                     autofocus: "true",
                     onfocusout: move |_| is_editing.set(false),
                     onfocusout: move |_| is_editing.set(false),
                     onkeydown: move |evt| {
                     onkeydown: move |evt| {
-                        match evt.key.as_str() {
-                            "Enter" | "Escape" | "Tab" => is_editing.set(false),
+                        match evt.key() {
+                            Key::Enter | Key::Escape | Key::Tab => is_editing.set(false),
                             _ => {}
                             _ => {}
                         }
                         }
                     },
                     },

+ 85 - 0
examples/tui_all_events.rs

@@ -0,0 +1,85 @@
+use dioxus::prelude::*;
+use dioxus_html::on::{FocusData, KeyboardData, MouseData, WheelData};
+use std::sync::Arc;
+
+fn main() {
+    dioxus::tui::launch(app);
+}
+
+#[derive(Debug)]
+enum Event {
+    MouseMove(Arc<MouseData>),
+    MouseClick(Arc<MouseData>),
+    MouseDoubleClick(Arc<MouseData>),
+    MouseDown(Arc<MouseData>),
+    MouseUp(Arc<MouseData>),
+
+    Wheel(Arc<WheelData>),
+
+    KeyDown(Arc<KeyboardData>),
+    KeyUp(Arc<KeyboardData>),
+    KeyPress(Arc<KeyboardData>),
+
+    FocusIn(Arc<FocusData>),
+    FocusOut(Arc<FocusData>),
+}
+
+const MAX_EVENTS: usize = 8;
+
+fn app(cx: Scope) -> Element {
+    let events = use_ref(&cx, || Vec::new());
+
+    let events_lock = events.read();
+    let first_index = events_lock.len().saturating_sub(MAX_EVENTS);
+    let events_rendered = events_lock[first_index..].iter().map(|event| {
+        // TUI panics if text overflows (https://github.com/DioxusLabs/dioxus/issues/371)
+        // temporary hack: just trim the strings (and make sure viewport is big enough)
+        // todo: remove
+        let mut trimmed = format!("{event:?}");
+        trimmed.truncate(200);
+        cx.render(rsx!(p { "{trimmed}" }))
+    });
+
+    let log_event = move |event: Event| {
+        events.write().push(event);
+    };
+
+    cx.render(rsx! {
+        div {
+            width: "100%",
+            height: "100%",
+            flex_direction: "column",
+            div {
+                width: "80%",
+                height: "50%",
+                border_width: "1px",
+                justify_content: "center",
+                align_items: "center",
+                background_color: "hsl(248, 53%, 58%)",
+
+                onmousemove: move |event| log_event(Event::MouseMove(event.data)),
+                onclick: move |event| log_event(Event::MouseClick(event.data)),
+                ondblclick: move |event| log_event(Event::MouseDoubleClick(event.data)),
+                onmousedown: move |event| log_event(Event::MouseDown(event.data)),
+                onmouseup: move |event| log_event(Event::MouseUp(event.data)),
+
+                onwheel: move |event| log_event(Event::Wheel(event.data)),
+
+                onkeydown: move |event| log_event(Event::KeyDown(event.data)),
+                onkeyup: move |event| log_event(Event::KeyUp(event.data)),
+                onkeypress: move |event| log_event(Event::KeyPress(event.data)),
+
+                onfocusin: move |event| log_event(Event::FocusIn(event.data)),
+                onfocusout: move |event| log_event(Event::FocusOut(event.data)),
+
+                "Hover, click, type or scroll to see the info down below"
+            },
+            div {
+                width: "80%",
+                height: "50%",
+                flex_direction: "column",
+                events_rendered,
+            },
+        },
+    })
+}

+ 1 - 1
examples/tui_border.rs

@@ -14,7 +14,7 @@ fn app(cx: Scope) -> Element {
             justify_content: "center",
             justify_content: "center",
             align_items: "center",
             align_items: "center",
             background_color: "hsl(248, 53%, 58%)",
             background_color: "hsl(248, 53%, 58%)",
-            onwheel: move |w| radius.modify(|r| (r + w.delta_y as i8).abs()),
+            onwheel: move |w| radius.modify(|r| (r + w.delta().strip_units().y as i8).abs()),
 
 
             border_style: "solid none solid double",
             border_style: "solid none solid double",
             border_width: "thick",
             border_width: "thick",

+ 3 - 2
examples/tui_buttons.rs

@@ -1,4 +1,5 @@
-use dioxus::{events::KeyCode, prelude::*};
+use dioxus::prelude::*;
+use dioxus_html::input_data::keyboard_types::Code;
 
 
 fn main() {
 fn main() {
     dioxus::tui::launch(app);
     dioxus::tui::launch(app);
@@ -27,7 +28,7 @@ fn Button(cx: Scope<ButtonProps>) -> Element {
             background_color: "{color}",
             background_color: "{color}",
             tabindex: "{cx.props.layer}",
             tabindex: "{cx.props.layer}",
             onkeydown: |e| {
             onkeydown: |e| {
-                if let KeyCode::Space = e.data.key_code{
+                if let Code::Space = e.data.code() {
                     toggle.modify(|f| !f);
                     toggle.modify(|f| !f);
                 }
                 }
             },
             },

+ 4 - 4
examples/tui_hover.rs

@@ -65,7 +65,7 @@ fn app(cx: Scope) -> Element {
                     onmouseenter: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
                     onmouseenter: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
                     onmousedown: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
                     onmousedown: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
                     onmouseup: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
                     onmouseup: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
-                    onwheel: move |w| q1_color.set([q1_color[0] + (10.0*w.delta_y) as i32, 0, 0]),
+                    onwheel: move |w| q1_color.set([q1_color[0] + (10.0*w.delta().strip_units().y) as i32, 0, 0]),
                     onmouseleave: move |_| q1_color.set([200; 3]),
                     onmouseleave: move |_| q1_color.set([200; 3]),
                     onmousemove: update_data,
                     onmousemove: update_data,
                     "click me"
                     "click me"
@@ -79,7 +79,7 @@ fn app(cx: Scope) -> Element {
                     onmouseenter: move |m| q2_color.set([get_brightness(m.data); 3]),
                     onmouseenter: move |m| q2_color.set([get_brightness(m.data); 3]),
                     onmousedown: move |m| q2_color.set([get_brightness(m.data); 3]),
                     onmousedown: move |m| q2_color.set([get_brightness(m.data); 3]),
                     onmouseup: move |m| q2_color.set([get_brightness(m.data); 3]),
                     onmouseup: move |m| q2_color.set([get_brightness(m.data); 3]),
-                    onwheel: move |w| q2_color.set([q2_color[0] + (10.0*w.delta_y) as i32;3]),
+                    onwheel: move |w| q2_color.set([q2_color[0] + (10.0*w.delta().strip_units().y) as i32;3]),
                     onmouseleave: move |_| q2_color.set([200; 3]),
                     onmouseleave: move |_| q2_color.set([200; 3]),
                     onmousemove: update_data,
                     onmousemove: update_data,
                     "click me"
                     "click me"
@@ -99,7 +99,7 @@ fn app(cx: Scope) -> Element {
                     onmouseenter: move |m| q3_color.set([0, get_brightness(m.data), 0]),
                     onmouseenter: move |m| q3_color.set([0, get_brightness(m.data), 0]),
                     onmousedown: move |m| q3_color.set([0, get_brightness(m.data), 0]),
                     onmousedown: move |m| q3_color.set([0, get_brightness(m.data), 0]),
                     onmouseup: move |m| q3_color.set([0, get_brightness(m.data), 0]),
                     onmouseup: move |m| q3_color.set([0, get_brightness(m.data), 0]),
-                    onwheel: move |w| q3_color.set([0, q3_color[1] + (10.0*w.delta_y) as i32, 0]),
+                    onwheel: move |w| q3_color.set([0, q3_color[1] + (10.0*w.delta().strip_units().y) as i32, 0]),
                     onmouseleave: move |_| q3_color.set([200; 3]),
                     onmouseleave: move |_| q3_color.set([200; 3]),
                     onmousemove: update_data,
                     onmousemove: update_data,
                     "click me"
                     "click me"
@@ -113,7 +113,7 @@ fn app(cx: Scope) -> Element {
                     onmouseenter: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
                     onmouseenter: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
                     onmousedown: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
                     onmousedown: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
                     onmouseup: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
                     onmouseup: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
-                    onwheel: move |w| q4_color.set([0, 0, q4_color[2] + (10.0*w.delta_y) as i32]),
+                    onwheel: move |w| q4_color.set([0, 0, q4_color[2] + (10.0*w.delta().strip_units().y) as i32]),
                     onmouseleave: move |_| q4_color.set([200; 3]),
                     onmouseleave: move |_| q4_color.set([200; 3]),
                     onmousemove: update_data,
                     onmousemove: update_data,
                     "click me"
                     "click me"

+ 18 - 12
examples/tui_keys.rs

@@ -1,9 +1,9 @@
 use dioxus::events::WheelEvent;
 use dioxus::events::WheelEvent;
 use dioxus::prelude::*;
 use dioxus::prelude::*;
 use dioxus_html::geometry::ScreenPoint;
 use dioxus_html::geometry::ScreenPoint;
+use dioxus_html::input_data::keyboard_types::Code;
 use dioxus_html::input_data::MouseButtonSet;
 use dioxus_html::input_data::MouseButtonSet;
 use dioxus_html::on::{KeyboardEvent, MouseEvent};
 use dioxus_html::on::{KeyboardEvent, MouseEvent};
-use dioxus_html::KeyCode;
 
 
 fn main() {
 fn main() {
     dioxus::tui::launch(app);
     dioxus::tui::launch(app);
@@ -16,6 +16,21 @@ fn app(cx: Scope) -> Element {
     let buttons = use_state(&cx, MouseButtonSet::empty);
     let buttons = use_state(&cx, MouseButtonSet::empty);
     let mouse_clicked = use_state(&cx, || false);
     let mouse_clicked = use_state(&cx, || false);
 
 
+    let key_down_handler = move |evt: KeyboardEvent| {
+        match evt.data.code() {
+            Code::ArrowLeft => count.set(count + 1),
+            Code::ArrowRight => count.set(count - 1),
+            Code::ArrowUp => count.set(count + 10),
+            Code::ArrowDown => count.set(count - 10),
+            _ => {}
+        }
+        key.set(format!(
+            "{:?} repeating: {:?}",
+            evt.key(),
+            evt.is_auto_repeating()
+        ));
+    };
+
     cx.render(rsx! {
     cx.render(rsx! {
         div {
         div {
             width: "100%",
             width: "100%",
@@ -24,18 +39,9 @@ fn app(cx: Scope) -> Element {
             justify_content: "center",
             justify_content: "center",
             align_items: "center",
             align_items: "center",
             flex_direction: "column",
             flex_direction: "column",
-            onkeydown: move |evt: KeyboardEvent| {
-                match evt.data.key_code {
-                    KeyCode::LeftArrow => count.set(count + 1),
-                    KeyCode::RightArrow => count.set(count - 1),
-                    KeyCode::UpArrow => count.set(count + 10),
-                    KeyCode::DownArrow => count.set(count - 10),
-                    _ => {},
-                }
-                key.set(format!("{:?} repeating: {:?}", evt.key, evt.repeat));
-            },
+            onkeydown: key_down_handler,
             onwheel: move |evt: WheelEvent| {
             onwheel: move |evt: WheelEvent| {
-                count.set(count + evt.data.delta_y as i64);
+                count.set(count + evt.data.delta().strip_units().y as i64);
             },
             },
             ondrag: move |evt: MouseEvent| {
             ondrag: move |evt: MouseEvent| {
                 mouse.set(evt.data.screen_coordinates());
                 mouse.set(evt.data.screen_coordinates());

+ 1 - 1
examples/tui_text.rs

@@ -12,7 +12,7 @@ fn app(cx: Scope) -> Element {
             width: "100%",
             width: "100%",
             height: "100%",
             height: "100%",
             flex_direction: "column",
             flex_direction: "column",
-            onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)),
+            onwheel: move |evt| alpha.set((**alpha + evt.data.delta().strip_units().y as i64).min(100).max(0)),
 
 
             p {
             p {
                 background_color: "black",
                 background_color: "black",

+ 1 - 4
packages/core/src/scopes.rs

@@ -835,10 +835,7 @@ impl ScopeState {
     /// }
     /// }
     /// ```
     /// ```
     #[allow(clippy::mut_from_ref)]
     #[allow(clippy::mut_from_ref)]
-    pub fn use_hook<'src, State: 'static>(
-        &'src self,
-        initializer: impl FnOnce(usize) -> State,
-    ) -> &'src mut State {
+    pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce(usize) -> State) -> &mut State {
         let mut vals = self.hook_vals.borrow_mut();
         let mut vals = self.hook_vals.borrow_mut();
 
 
         let hook_len = vals.len();
         let hook_len = vals.len();

+ 2 - 0
packages/desktop/src/controller.rs

@@ -86,6 +86,8 @@ impl DesktopController {
                                         (serde_json::to_string(&err).unwrap() + "\n").as_bytes(),
                                         (serde_json::to_string(&err).unwrap() + "\n").as_bytes(),
                                     )
                                     )
                                     .unwrap();
                                     .unwrap();
+                            } else {
+                                panic!("{}", err);
                             }
                             }
                         }
                         }
                     }
                     }

+ 1 - 1
packages/hooks/src/use_shared_state.rs

@@ -60,7 +60,7 @@ impl<T> ProvidedStateInner<T> {
 ///
 ///
 ///
 ///
 ///
 ///
-pub fn use_context<'a, T: 'static>(cx: &'a ScopeState) -> Option<UseSharedState<'a, T>> {
+pub fn use_context<T: 'static>(cx: &ScopeState) -> Option<UseSharedState<T>> {
     let state = cx.use_hook(|_| {
     let state = cx.use_hook(|_| {
         let scope_id = cx.scope_id();
         let scope_id = cx.scope_id();
         let root = cx.consume_context::<ProvidedState<T>>();
         let root = cx.consume_context::<ProvidedState<T>>();

+ 1 - 4
packages/hooks/src/useref.rs

@@ -110,10 +110,7 @@ use std::{
 ///     }
 ///     }
 /// })
 /// })
 /// ```
 /// ```
-pub fn use_ref<'a, T: 'static>(
-    cx: &'a ScopeState,
-    initialize_refcell: impl FnOnce() -> T,
-) -> &'a UseRef<T> {
+pub fn use_ref<T: 'static>(cx: &ScopeState, initialize_refcell: impl FnOnce() -> T) -> &UseRef<T> {
     let hook = cx.use_hook(|_| UseRef {
     let hook = cx.use_hook(|_| UseRef {
         update: cx.schedule_update(),
         update: cx.schedule_update(),
         value: Rc::new(RefCell::new(initialize_refcell())),
         value: Rc::new(RefCell::new(initialize_refcell())),

+ 6 - 6
packages/hooks/src/usestate.rs

@@ -30,10 +30,10 @@ use std::{
 ///     ))
 ///     ))
 /// }
 /// }
 /// ```
 /// ```
-pub fn use_state<'a, T: 'static>(
-    cx: &'a ScopeState,
+pub fn use_state<T: 'static>(
+    cx: &ScopeState,
     initial_state_fn: impl FnOnce() -> T,
     initial_state_fn: impl FnOnce() -> T,
-) -> &'a UseState<T> {
+) -> &UseState<T> {
     let hook = cx.use_hook(move |_| {
     let hook = cx.use_hook(move |_| {
         let current_val = Rc::new(initial_state_fn());
         let current_val = Rc::new(initial_state_fn());
         let update_callback = cx.schedule_update();
         let update_callback = cx.schedule_update();
@@ -304,13 +304,13 @@ impl<T: 'static> Clone for UseState<T> {
     }
     }
 }
 }
 
 
-impl<'a, T: 'static + Display> std::fmt::Display for UseState<T> {
+impl<T: 'static + Display> std::fmt::Display for UseState<T> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "{}", self.current_val)
         write!(f, "{}", self.current_val)
     }
     }
 }
 }
 
 
-impl<'a, T: std::fmt::Binary> std::fmt::Binary for UseState<T> {
+impl<T: std::fmt::Binary> std::fmt::Binary for UseState<T> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "{:b}", self.current_val.as_ref())
         write!(f, "{:b}", self.current_val.as_ref())
     }
     }
@@ -341,7 +341,7 @@ impl<T: Debug> Debug for UseState<T> {
     }
     }
 }
 }
 
 
-impl<'a, T> std::ops::Deref for UseState<T> {
+impl<T> std::ops::Deref for UseState<T> {
     type Target = T;
     type Target = T;
 
 
     fn deref(&self) -> &Self::Target {
     fn deref(&self) -> &Self::Target {

+ 1 - 1
packages/html/Cargo.toml

@@ -40,5 +40,5 @@ features = [
 
 
 [features]
 [features]
 default = []
 default = []
-serialize = ["serde", "serde_repr"]
+serialize = ["serde", "serde_repr", "euclid/serde", "keyboard-types/serde"]
 wasm-bind = ["web-sys", "wasm-bindgen"]
 wasm-bind = ["web-sys", "wasm-bindgen"]

+ 195 - 44
packages/html/src/events.rs

@@ -5,12 +5,20 @@ use dioxus_core::*;
 pub mod on {
 pub mod on {
     //! Input events and associated data
     //! Input events and associated data
 
 
-    use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
+    use crate::geometry::{
+        ClientPoint, Coordinates, ElementPoint, LinesVector, PagePoint, PagesVector, PixelsVector,
+        ScreenPoint, WheelDelta,
+    };
     use crate::input_data::{
     use crate::input_data::{
-        decode_mouse_button_set, encode_mouse_button_set, MouseButton, MouseButtonSet,
+        decode_key_location, decode_mouse_button_set, encode_key_location, encode_mouse_button_set,
+        MouseButton, MouseButtonSet,
     };
     };
-    use keyboard_types::Modifiers;
+    use euclid::UnknownUnit;
+    use keyboard_types::{Code, Key, Location, Modifiers};
     use std::collections::HashMap;
     use std::collections::HashMap;
+    use std::convert::TryInto;
+    use std::fmt::{Debug, Formatter};
+    use std::str::FromStr;
 
 
     use super::*;
     use super::*;
     macro_rules! event_directory {
     macro_rules! event_directory {
@@ -419,71 +427,142 @@ pub mod on {
 
 
     pub type KeyboardEvent = UiEvent<KeyboardData>;
     pub type KeyboardEvent = UiEvent<KeyboardData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug, Clone)]
+    #[derive(Clone)]
     pub struct KeyboardData {
     pub struct KeyboardData {
+        #[deprecated(
+            since = "0.3.0",
+            note = "This may not work in all environments. Use key() instead."
+        )]
         pub char_code: u32,
         pub char_code: u32,
 
 
         /// Identify which "key" was entered.
         /// Identify which "key" was entered.
-        ///
-        /// This is the best method to use for all languages. They key gets mapped to a String sequence which you can match on.
-        /// The key isn't an enum because there are just so many context-dependent keys.
-        ///
-        /// A full list on which keys to use is available at:
-        /// <https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values>
-        ///
-        /// # Example
-        ///
-        /// ```rust, ignore
-        /// match event.key().as_str() {
-        ///     "Esc" | "Escape" => {}
-        ///     "ArrowDown" => {}
-        ///     "ArrowLeft" => {}
-        ///      _ => {}
-        /// }
-        /// ```
-        ///
+        #[deprecated(since = "0.3.0", note = "use key() instead")]
         pub key: String,
         pub key: String,
 
 
         /// Get the key code as an enum Variant.
         /// Get the key code as an enum Variant.
-        ///
-        /// This is intended for things like arrow keys, escape keys, function keys, and other non-international keys.
-        /// To match on unicode sequences, use the [`KeyboardData::key`] method - this will return a string identifier instead of a limited enum.
-        ///
-        ///
-        /// ## Example
-        ///
-        /// ```rust, ignore
-        /// use dioxus::KeyCode;
-        /// match event.key_code() {
-        ///     KeyCode::Escape => {}
-        ///     KeyCode::LeftArrow => {}
-        ///     KeyCode::RightArrow => {}
-        ///     _ => {}
-        /// }
-        /// ```
-        ///
+        #[deprecated(
+            since = "0.3.0",
+            note = "This may not work in all environments. Use code() instead."
+        )]
         pub key_code: KeyCode,
         pub key_code: KeyCode,
 
 
+        /// the physical key on the keyboard
+        code: Code,
+
         /// Indicate if the `alt` modifier key was pressed during this keyboard event
         /// Indicate if the `alt` modifier key was pressed during this keyboard event
+        #[deprecated(since = "0.3.0", note = "use modifiers() instead")]
         pub alt_key: bool,
         pub alt_key: bool,
 
 
         /// Indicate if the `ctrl` modifier key was pressed during this keyboard event
         /// Indicate if the `ctrl` modifier key was pressed during this keyboard event
+        #[deprecated(since = "0.3.0", note = "use modifiers() instead")]
         pub ctrl_key: bool,
         pub ctrl_key: bool,
 
 
         /// Indicate if the `meta` modifier key was pressed during this keyboard event
         /// Indicate if the `meta` modifier key was pressed during this keyboard event
+        #[deprecated(since = "0.3.0", note = "use modifiers() instead")]
         pub meta_key: bool,
         pub meta_key: bool,
 
 
         /// Indicate if the `shift` modifier key was pressed during this keyboard event
         /// Indicate if the `shift` modifier key was pressed during this keyboard event
+        #[deprecated(since = "0.3.0", note = "use modifiers() instead")]
         pub shift_key: bool,
         pub shift_key: bool,
 
 
-        pub locale: String,
-
+        #[deprecated(since = "0.3.0", note = "use location() instead")]
         pub location: usize,
         pub location: usize,
 
 
+        #[deprecated(since = "0.3.0", note = "use is_auto_repeating() instead")]
         pub repeat: bool,
         pub repeat: bool,
 
 
+        #[deprecated(since = "0.3.0", note = "use code() or key() instead")]
         pub which: usize,
         pub which: usize,
-        // get_modifier_state: bool,
+    }
+
+    impl KeyboardData {
+        pub fn new(
+            key: Key,
+            code: Code,
+            location: Location,
+            is_auto_repeating: bool,
+            modifiers: Modifiers,
+        ) -> Self {
+            #[allow(deprecated)]
+            KeyboardData {
+                char_code: key.legacy_charcode(),
+                key: key.to_string(),
+                key_code: KeyCode::from_raw_code(
+                    key.legacy_keycode()
+                        .try_into()
+                        .expect("could not convert keycode to u8"),
+                ),
+                code,
+                alt_key: modifiers.contains(Modifiers::ALT),
+                ctrl_key: modifiers.contains(Modifiers::CONTROL),
+                meta_key: modifiers.contains(Modifiers::META),
+                shift_key: modifiers.contains(Modifiers::SHIFT),
+                location: encode_key_location(location),
+                repeat: is_auto_repeating,
+                which: key
+                    .legacy_charcode()
+                    .try_into()
+                    .expect("could not convert charcode to usize"),
+            }
+        }
+
+        /// The value of the key pressed by the user, taking into consideration the state of modifier keys such as Shift as well as the keyboard locale and layout.
+        pub fn key(&self) -> Key {
+            #[allow(deprecated)]
+            FromStr::from_str(&self.key).expect("could not parse")
+        }
+
+        /// A physical key on the keyboard (as opposed to the character generated by pressing the key). In other words, this property returns a value that isn't altered by keyboard layout or the state of the modifier keys.
+        pub fn code(&self) -> Code {
+            self.code
+        }
+
+        /// The set of modifier keys which were pressed when the event occurred
+        pub fn modifiers(&self) -> Modifiers {
+            let mut modifiers = Modifiers::empty();
+
+            #[allow(deprecated)]
+            {
+                if self.alt_key {
+                    modifiers.insert(Modifiers::ALT);
+                }
+                if self.ctrl_key {
+                    modifiers.insert(Modifiers::CONTROL);
+                }
+                if self.meta_key {
+                    modifiers.insert(Modifiers::META);
+                }
+                if self.shift_key {
+                    modifiers.insert(Modifiers::SHIFT);
+                }
+            }
+
+            modifiers
+        }
+
+        /// The location of the key on the keyboard or other input device.
+        pub fn location(&self) -> Location {
+            #[allow(deprecated)]
+            decode_key_location(self.location)
+        }
+
+        /// `true` iff the key is being held down such that it is automatically repeating.
+        pub fn is_auto_repeating(&self) -> bool {
+            #[allow(deprecated)]
+            self.repeat
+        }
+    }
+
+    impl Debug for KeyboardData {
+        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+            f.debug_struct("KeyboardData")
+                .field("key", &self.key())
+                .field("code", &self.code())
+                .field("modifiers", &self.modifiers())
+                .field("location", &self.location())
+                .field("is_auto_repeating", &self.is_auto_repeating())
+                .finish()
+        }
     }
     }
 
 
     pub type FocusEvent = UiEvent<FocusData>;
     pub type FocusEvent = UiEvent<FocusData>;
@@ -502,7 +581,7 @@ pub mod on {
 
 
     pub type MouseEvent = UiEvent<MouseData>;
     pub type MouseEvent = UiEvent<MouseData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug, Clone)]
+    #[derive(Clone)]
     /// Data associated with a mouse event
     /// Data associated with a mouse event
     ///
     ///
     /// Do not use the deprecated fields; they may change or become private in the future.
     /// Do not use the deprecated fields; they may change or become private in the future.
@@ -687,6 +766,17 @@ pub mod on {
         }
         }
     }
     }
 
 
+    impl Debug for MouseData {
+        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+            f.debug_struct("MouseData")
+                .field("coordinates", &self.coordinates())
+                .field("modifiers", &self.modifiers())
+                .field("held_buttons", &self.held_buttons())
+                .field("trigger_button", &self.trigger_button())
+                .finish()
+        }
+    }
+
     pub type PointerEvent = UiEvent<PointerData>;
     pub type PointerEvent = UiEvent<PointerData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug, Clone)]
     #[derive(Debug, Clone)]
@@ -738,14 +828,75 @@ pub mod on {
 
 
     pub type WheelEvent = UiEvent<WheelData>;
     pub type WheelEvent = UiEvent<WheelData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug, Clone)]
+    #[derive(Clone)]
     pub struct WheelData {
     pub struct WheelData {
+        #[deprecated(since = "0.3.0", note = "use delta() instead")]
         pub delta_mode: u32,
         pub delta_mode: u32,
+        #[deprecated(since = "0.3.0", note = "use delta() instead")]
         pub delta_x: f64,
         pub delta_x: f64,
+        #[deprecated(since = "0.3.0", note = "use delta() instead")]
         pub delta_y: f64,
         pub delta_y: f64,
+        #[deprecated(since = "0.3.0", note = "use delta() instead")]
         pub delta_z: f64,
         pub delta_z: f64,
     }
     }
 
 
+    impl WheelData {
+        /// Construct a new WheelData with the specified wheel movement delta
+        pub fn new(delta: WheelDelta) -> Self {
+            let (delta_mode, vector) = match delta {
+                WheelDelta::Pixels(v) => (0, v.cast_unit::<UnknownUnit>()),
+                WheelDelta::Lines(v) => (1, v.cast_unit::<UnknownUnit>()),
+                WheelDelta::Pages(v) => (2, v.cast_unit::<UnknownUnit>()),
+            };
+
+            #[allow(deprecated)]
+            WheelData {
+                delta_mode,
+                delta_x: vector.x,
+                delta_y: vector.y,
+                delta_z: vector.z,
+            }
+        }
+
+        /// Construct from the attributes of the web wheel event
+        pub fn from_web_attributes(
+            delta_mode: u32,
+            delta_x: f64,
+            delta_y: f64,
+            delta_z: f64,
+        ) -> Self {
+            #[allow(deprecated)]
+            Self {
+                delta_mode,
+                delta_x,
+                delta_y,
+                delta_z,
+            }
+        }
+
+        /// The amount of wheel movement
+        #[allow(deprecated)]
+        pub fn delta(&self) -> WheelDelta {
+            let x = self.delta_x;
+            let y = self.delta_y;
+            let z = self.delta_z;
+            match self.delta_mode {
+                0 => WheelDelta::Pixels(PixelsVector::new(x, y, z)),
+                1 => WheelDelta::Lines(LinesVector::new(x, y, z)),
+                2 => WheelDelta::Pages(PagesVector::new(x, y, z)),
+                _ => panic!("Invalid delta mode, {:?}", self.delta_mode),
+            }
+        }
+    }
+
+    impl Debug for WheelData {
+        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+            f.debug_struct("WheelData")
+                .field("delta", &self.delta())
+                .finish()
+        }
+    }
+
     pub type MediaEvent = UiEvent<MediaData>;
     pub type MediaEvent = UiEvent<MediaData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
     #[derive(Debug, Clone)]
     #[derive(Debug, Clone)]

+ 71 - 0
packages/html/src/geometry.rs

@@ -25,7 +25,78 @@ pub struct PageSpace;
 /// A point in PageSpace
 /// A point in PageSpace
 pub type PagePoint = Point2D<f64, PageSpace>;
 pub type PagePoint = Point2D<f64, PageSpace>;
 
 
+/// A pixel unit: one unit corresponds to 1 pixel
+pub struct Pixels;
+/// A vector expressed in Pixels
+pub type PixelsVector = Vector3D<f64, Pixels>;
+
+/// A unit in terms of Lines
+///
+/// One unit is relative to the size of one line
+pub struct Lines;
+/// A vector expressed in Lines
+pub type LinesVector = Vector3D<f64, Lines>;
+
+/// A unit in terms of Screens:
+///
+/// One unit is relative to the size of a page
+pub struct Pages;
+/// A vector expressed in Pages
+pub type PagesVector = Vector3D<f64, Pages>;
+
+/// A vector representing the amount the mouse wheel was moved
+///
+/// This may be expressed in Pixels, Lines or Pages
+#[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+pub enum WheelDelta {
+    /// Movement in Pixels
+    Pixels(PixelsVector),
+    /// Movement in Lines
+    Lines(LinesVector),
+    /// Movement in Pages
+    Pages(PagesVector),
+}
+
+impl WheelDelta {
+    /// Convenience function for constructing a WheelDelta with pixel units
+    pub fn pixels(x: f64, y: f64, z: f64) -> Self {
+        WheelDelta::Pixels(PixelsVector::new(x, y, z))
+    }
+
+    /// Convenience function for constructing a WheelDelta with line units
+    pub fn lines(x: f64, y: f64, z: f64) -> Self {
+        WheelDelta::Lines(LinesVector::new(x, y, z))
+    }
+
+    /// Convenience function for constructing a WheelDelta with page units
+    pub fn pages(x: f64, y: f64, z: f64) -> Self {
+        WheelDelta::Pages(PagesVector::new(x, y, z))
+    }
+
+    /// Returns true iff there is no wheel movement
+    ///
+    /// i.e. the x, y and z delta is zero (disregards units)
+    pub fn is_zero(&self) -> bool {
+        self.strip_units() == Vector3D::new(0., 0., 0.)
+    }
+
+    /// A Vector3D proportional to the amount scrolled
+    ///
+    /// Note that this disregards the 3 possible units: this could be expressed in terms of pixels, lines, or pages.
+    ///
+    /// In most cases, to properly handle scrolling, you should handle all 3 possible enum variants instead of stripping units. Otherwise, if you assume that the units will always be pixels, the user may experience some unexpectedly slow scrolling if their mouse/OS sends values expressed in lines or pages.
+    pub fn strip_units(&self) -> Vector3D<f64, UnknownUnit> {
+        match self {
+            WheelDelta::Pixels(v) => v.cast_unit(),
+            WheelDelta::Lines(v) => v.cast_unit(),
+            WheelDelta::Pages(v) => v.cast_unit(),
+        }
+    }
+}
+
 /// Coordinates of a point in the app's interface
 /// Coordinates of a point in the app's interface
+#[derive(Debug)]
 pub struct Coordinates {
 pub struct Coordinates {
     screen: ScreenPoint,
     screen: ScreenPoint,
     client: ClientPoint,
     client: ClientPoint,

+ 23 - 0
packages/html/src/input_data.rs

@@ -3,6 +3,7 @@ use enumset::{EnumSet, EnumSetType};
 
 
 /// A re-export of keyboard_types
 /// A re-export of keyboard_types
 pub use keyboard_types;
 pub use keyboard_types;
+use keyboard_types::Location;
 
 
 /// A mouse button type (such as Primary/Secondary)
 /// A mouse button type (such as Primary/Secondary)
 // note: EnumSetType also derives Copy and Clone for some reason
 // note: EnumSetType also derives Copy and Clone for some reason
@@ -118,3 +119,25 @@ pub fn encode_mouse_button_set(set: MouseButtonSet) -> u16 {
 
 
     code
     code
 }
 }
+
+pub fn decode_key_location(code: usize) -> Location {
+    match code {
+        0 => Location::Standard,
+        1 => Location::Left,
+        2 => Location::Right,
+        3 => Location::Numpad,
+        // keyboard_types doesn't yet support mobile/joystick locations
+        4 | 5 => Location::Standard,
+        // unknown location; Standard seems better than panicking
+        _ => Location::Standard,
+    }
+}
+
+pub fn encode_key_location(location: Location) -> usize {
+    match location {
+        Location::Standard => 0,
+        Location::Left => 1,
+        Location::Right => 2,
+        Location::Numpad => 3,
+    }
+}

+ 30 - 21
packages/html/src/web_sys_bind/events.rs

@@ -1,11 +1,12 @@
 use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
 use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
-use crate::input_data::{decode_mouse_button_set, MouseButton};
+use crate::input_data::{decode_key_location, decode_mouse_button_set, MouseButton};
 use crate::on::{
 use crate::on::{
     AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData,
     AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData,
     TransitionData, WheelData,
     TransitionData, WheelData,
 };
 };
-use crate::KeyCode;
-use keyboard_types::Modifiers;
+use keyboard_types::{Code, Key, Modifiers};
+use std::convert::TryInto;
+use std::str::FromStr;
 use wasm_bindgen::JsCast;
 use wasm_bindgen::JsCast;
 use web_sys::{
 use web_sys::{
     AnimationEvent, CompositionEvent, Event, KeyboardEvent, MouseEvent, PointerEvent, TouchEvent,
     AnimationEvent, CompositionEvent, Event, KeyboardEvent, MouseEvent, PointerEvent, TouchEvent,
@@ -56,19 +57,32 @@ impl From<&CompositionEvent> for CompositionData {
 
 
 impl From<&KeyboardEvent> for KeyboardData {
 impl From<&KeyboardEvent> for KeyboardData {
     fn from(e: &KeyboardEvent) -> Self {
     fn from(e: &KeyboardEvent) -> Self {
-        Self {
-            alt_key: e.alt_key(),
-            char_code: e.char_code(),
-            key: e.key(),
-            key_code: KeyCode::from_raw_code(e.key_code() as u8),
-            ctrl_key: e.ctrl_key(),
-            locale: "not implemented".to_string(),
-            location: e.location() as usize,
-            meta_key: e.meta_key(),
-            repeat: e.repeat(),
-            shift_key: e.shift_key(),
-            which: e.which() as usize,
+        let mut modifiers = Modifiers::empty();
+
+        if e.alt_key() {
+            modifiers.insert(Modifiers::ALT);
+        }
+        if e.ctrl_key() {
+            modifiers.insert(Modifiers::CONTROL);
         }
         }
+        if e.meta_key() {
+            modifiers.insert(Modifiers::META);
+        }
+        if e.shift_key() {
+            modifiers.insert(Modifiers::SHIFT);
+        }
+
+        Self::new(
+            Key::from_str(&e.key()).expect("could not parse key"),
+            Code::from_str(&e.code()).expect("could not parse code"),
+            decode_key_location(
+                e.location()
+                    .try_into()
+                    .expect("could not convert location to u32"),
+            ),
+            e.repeat(),
+            modifiers,
+        )
     }
     }
 }
 }
 
 
@@ -146,12 +160,7 @@ impl From<&PointerEvent> for PointerData {
 
 
 impl From<&WheelEvent> for WheelData {
 impl From<&WheelEvent> for WheelData {
     fn from(e: &WheelEvent) -> Self {
     fn from(e: &WheelEvent) -> Self {
-        Self {
-            delta_x: e.delta_x(),
-            delta_y: e.delta_y(),
-            delta_z: e.delta_z(),
-            delta_mode: e.delta_mode(),
-        }
+        WheelData::from_web_attributes(e.delta_mode(), e.delta_x(), e.delta_y(), e.delta_z())
     }
     }
 }
 }
 
 

+ 45 - 44
packages/interpreter/src/interpreter.js

@@ -15,41 +15,43 @@ class ListenerMap {
     this.root = root;
     this.root = root;
   }
   }
 
 
-  createBubbling(event_name, handler) {
-    if (this.global[event_name] === undefined) {
-      this.global[event_name] = {};
-      this.global[event_name].active = 1;
-      this.global[event_name].callback = handler;
-      this.root.addEventListener(event_name, handler);
-    } else {
-      this.global[event_name].active++;
+  create(event_name, element, handler, bubbles) {
+    if (bubbles) {
+      if (this.global[event_name] === undefined) {
+        this.global[event_name] = {};
+        this.global[event_name].active = 1;
+        this.global[event_name].callback = handler;
+        this.root.addEventListener(event_name, handler);
+      } else {
+        this.global[event_name].active++;
+      }
     }
     }
-  }
-
-  createNonBubbling(event_name, element, handler) {
-    const id = element.getAttribute("data-dioxus-id");
-    if (!this.local[id]) {
-      this.local[id] = {};
+    else {
+      const id = element.getAttribute("data-dioxus-id");
+      if (!this.local[id]) {
+        this.local[id] = {};
+      }
+      this.local[id][event_name] = handler;
+      element.addEventListener(event_name, handler);
     }
     }
-    this.local[id][event_name] = handler;
-    element.addEventListener(event_name, handler);
   }
   }
 
 
-  removeBubbling(event_name) {
-    this.global[event_name].active--;
-    if (this.global[event_name].active === 0) {
-      this.root.removeEventListener(event_name, this.global[event_name].callback);
-      delete this.global[event_name];
+  remove(element, event_name, bubbles) {
+    if (bubbles) {
+      this.global[event_name].active--;
+      if (this.global[event_name].active === 0) {
+        this.root.removeEventListener(event_name, this.global[event_name].callback);
+        delete this.global[event_name];
+      }
     }
     }
-  }
-
-  removeNonBubbling(element, event_name) {
-    const id = element.getAttribute("data-dioxus-id");
-    delete this.local[id][event_name];
-    if (this.local[id].length === 0) {
-      delete this.local[id];
+    else {
+      const id = element.getAttribute("data-dioxus-id");
+      delete this.local[id][event_name];
+      if (this.local[id].length === 0) {
+        delete this.local[id];
+      }
+      element.removeEventListener(event_name, handler);
     }
     }
-    element.removeEventListener(event_name, handler);
   }
   }
 
 
   removeAllNonBubbling(element) {
   removeAllNonBubbling(element) {
@@ -93,7 +95,9 @@ export class Interpreter {
   ReplaceWith(root_id, m) {
   ReplaceWith(root_id, m) {
     let root = this.nodes[root_id];
     let root = this.nodes[root_id];
     let els = this.stack.splice(this.stack.length - m);
     let els = this.stack.splice(this.stack.length - m);
-    this.listeners.removeAllNonBubbling(root);
+    if (is_element_node(root.nodeType)) {
+      this.listeners.removeAllNonBubbling(root);
+    }
     root.replaceWith(...els);
     root.replaceWith(...els);
   }
   }
   InsertAfter(root, n) {
   InsertAfter(root, n) {
@@ -108,8 +112,10 @@ export class Interpreter {
   }
   }
   Remove(root) {
   Remove(root) {
     let node = this.nodes[root];
     let node = this.nodes[root];
-    this.listeners.removeAllNonBubbling(node);
     if (node !== undefined) {
     if (node !== undefined) {
+      if (is_element_node(node)) {
+        this.listeners.removeAllNonBubbling(node);
+      }
       node.remove();
       node.remove();
     }
     }
   }
   }
@@ -137,22 +143,12 @@ export class Interpreter {
   NewEventListener(event_name, root, handler, bubbles) {
   NewEventListener(event_name, root, handler, bubbles) {
     const element = this.nodes[root];
     const element = this.nodes[root];
     element.setAttribute("data-dioxus-id", `${root}`);
     element.setAttribute("data-dioxus-id", `${root}`);
-    if (bubbles) {
-      this.listeners.createBubbling(event_name, handler);
-    }
-    else {
-      this.listeners.createNonBubbling(event_name, element, handler);
-    }
+    this.listeners.create(event_name, element, handler, bubbles);
   }
   }
   RemoveEventListener(root, event_name, bubbles) {
   RemoveEventListener(root, event_name, bubbles) {
     const element = this.nodes[root];
     const element = this.nodes[root];
     element.removeAttribute(`data-dioxus-id`);
     element.removeAttribute(`data-dioxus-id`);
-    if (bubbles) {
-      this.listeners.removeBubbling(event_name)
-    }
-    else {
-      this.listeners.removeNonBubbling(element, event_name);
-    }
+    this.listeners.remove(element, event_name, bubbles);
   }
   }
   SetText(root, text) {
   SetText(root, text) {
     this.nodes[root].textContent = text;
     this.nodes[root].textContent = text;
@@ -388,6 +384,7 @@ export function serialize_event(event) {
         location,
         location,
         repeat,
         repeat,
         which,
         which,
+        code,
       } = event;
       } = event;
       return {
       return {
         char_code: charCode,
         char_code: charCode,
@@ -400,7 +397,7 @@ export function serialize_event(event) {
         location: location,
         location: location,
         repeat: repeat,
         repeat: repeat,
         which: which,
         which: which,
-        locale: "locale",
+        code,
       };
       };
     }
     }
     case "focus":
     case "focus":
@@ -660,6 +657,10 @@ const bool_attrs = {
   truespeed: true,
   truespeed: true,
 };
 };
 
 
+function is_element_node(node) {
+  return node.nodeType == 1;
+}
+
 function event_bubbles(event) {
 function event_bubbles(event) {
   switch (event) {
   switch (event) {
     case "copy":
     case "copy":

+ 0 - 1
packages/liveview/src/interpreter.js

@@ -358,7 +358,6 @@ function serialize_event(event) {
         location: location,
         location: location,
         repeat: repeat,
         repeat: repeat,
         which: which,
         which: which,
-        locale: "locale",
       };
       };
     }
     }
     case "focus":
     case "focus":

+ 3 - 3
packages/router/tests/web_router.rs

@@ -1,9 +1,9 @@
 #![cfg(target_arch = "wasm32")]
 #![cfg(target_arch = "wasm32")]
+#![allow(non_snake_case)]
 
 
 use dioxus::prelude::*;
 use dioxus::prelude::*;
 use dioxus_router::*;
 use dioxus_router::*;
 use gloo_utils::document;
 use gloo_utils::document;
-use serde::{Deserialize, Serialize};
 use wasm_bindgen_test::*;
 use wasm_bindgen_test::*;
 
 
 wasm_bindgen_test_configure!(run_in_browser);
 wasm_bindgen_test_configure!(run_in_browser);
@@ -47,7 +47,7 @@ fn simple_test() {
     }
     }
 
 
     fn BlogPost(cx: Scope) -> Element {
     fn BlogPost(cx: Scope) -> Element {
-        let id = use_route(&cx).parse_segment::<usize>("id")?;
+        let _id = use_route(&cx).parse_segment::<usize>("id")?;
 
 
         cx.render(rsx! {
         cx.render(rsx! {
             div {
             div {
@@ -58,5 +58,5 @@ fn simple_test() {
 
 
     main();
     main();
 
 
-    let element = gloo_utils::document();
+    let _ = document();
 }
 }

+ 2 - 2
packages/rsx_interpreter/src/captuered_context.rs

@@ -113,9 +113,9 @@ impl ToTokens for CapturedContextBuilder {
                     let expr = segment.to_token_stream();
                     let expr = segment.to_token_stream();
                     let as_string = expr.to_string();
                     let as_string = expr.to_string();
                     let format_expr = if format_args.is_empty() {
                     let format_expr = if format_args.is_empty() {
-                        "{".to_string() + format_args + "}"
+                        "{".to_string() + &format_args + "}"
                     } else {
                     } else {
-                        "{".to_string() + ":" + format_args + "}"
+                        "{".to_string() + ":" + &format_args + "}"
                     };
                     };
                     Some(quote! {
                     Some(quote! {
                         FormattedArg{
                         FormattedArg{

+ 17 - 0
packages/rsx_interpreter/src/error.rs

@@ -1,3 +1,5 @@
+use std::fmt::Display;
+
 use serde::{Deserialize, Serialize};
 use serde::{Deserialize, Serialize};
 
 
 use crate::CodeLocation;
 use crate::CodeLocation;
@@ -37,3 +39,18 @@ impl ParseError {
         ParseError { message, location }
         ParseError { message, location }
     }
     }
 }
 }
+
+impl Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Error::ParseError(error) => write!(
+                f,
+                "parse error:\n--> at {}:{}:{}\n\t{:?}\n",
+                error.location.file_path, error.location.line, error.location.column, error.message
+            ),
+            Error::RecompileRequiredError(reason) => {
+                write!(f, "recompile required: {:?}\n", reason)
+            }
+        }
+    }
+}

+ 12 - 4
packages/rsx_interpreter/src/interperter.rs

@@ -39,7 +39,7 @@ fn resolve_ifmt(ifmt: &IfmtInput, captured: &IfmtArgs) -> Result<String, Error>
                     }
                     }
                 }
                 }
             }
             }
-            Segment::Literal(lit) => result.push_str(lit),
+            Segment::Literal(lit) => result.push_str(&lit),
         }
         }
     }
     }
     Ok(result)
     Ok(result)
@@ -126,12 +126,12 @@ fn build_node<'a>(
 
 
                         ElementAttr::AttrExpression { .. }
                         ElementAttr::AttrExpression { .. }
                         | ElementAttr::CustomAttrExpression { .. } => {
                         | ElementAttr::CustomAttrExpression { .. } => {
-                            let (name, value) = match &attr.attr {
+                            let (name, value, span) = match &attr.attr {
                                 ElementAttr::AttrExpression { name, value } => {
                                 ElementAttr::AttrExpression { name, value } => {
-                                    (name.to_string(), value)
+                                    (name.to_string(), value, name.span())
                                 }
                                 }
                                 ElementAttr::CustomAttrExpression { name, value } => {
                                 ElementAttr::CustomAttrExpression { name, value } => {
-                                    (name.value(), value)
+                                    (name.value(), value, name.span())
                                 }
                                 }
                                 _ => unreachable!(),
                                 _ => unreachable!(),
                             };
                             };
@@ -151,6 +151,14 @@ fn build_node<'a>(
                                         is_volatile: false,
                                         is_volatile: false,
                                         namespace,
                                         namespace,
                                     });
                                     });
+                                } else {
+                                    return Err(Error::ParseError(ParseError::new(
+                                        syn::Error::new(
+                                            span,
+                                            format!("unknown attribute: {}", name),
+                                        ),
+                                        ctx.location.clone(),
+                                    )));
                                 }
                                 }
                             } else {
                             } else {
                                 return Err(Error::RecompileRequiredError(
                                 return Err(Error::RecompileRequiredError(

+ 2 - 0
packages/rsx_interpreter/src/lib.rs

@@ -150,6 +150,8 @@ impl RsxContext {
     fn report_error(&self, error: Error) {
     fn report_error(&self, error: Error) {
         if let Some(handler) = &self.data.write().unwrap().error_handler {
         if let Some(handler) = &self.data.write().unwrap().error_handler {
             handler.handle_error(error)
             handler.handle_error(error)
+        } else {
+            panic!("no error handler set for this platform...\n{}", error);
         }
         }
     }
     }
 
 

+ 241 - 158
packages/tui/src/hooks.rs

@@ -5,11 +5,13 @@ use dioxus_core::*;
 use fxhash::{FxHashMap, FxHashSet};
 use fxhash::{FxHashMap, FxHashSet};
 
 
 use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D};
 use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D};
-use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
-use dioxus_html::input_data::keyboard_types::Modifiers;
+use dioxus_html::geometry::{
+    ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint, WheelDelta,
+};
+use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers};
 use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
 use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
 use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
 use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
-use dioxus_html::{event_bubbles, on::*, KeyCode};
+use dioxus_html::{event_bubbles, on::*};
 use std::{
 use std::{
     any::Any,
     any::Any,
     cell::{RefCell, RefMut},
     cell::{RefCell, RefMut},
@@ -140,14 +142,20 @@ impl InnerInputState {
             EventData::Wheel(ref w) => self.wheel = Some(w.clone()),
             EventData::Wheel(ref w) => self.wheel = Some(w.clone()),
             EventData::Screen(ref s) => self.screen = Some(*s),
             EventData::Screen(ref s) => self.screen = Some(*s),
             EventData::Keyboard(ref mut k) => {
             EventData::Keyboard(ref mut k) => {
-                let repeat = self
+                let is_repeating = self
                     .last_key_pressed
                     .last_key_pressed
                     .as_ref()
                     .as_ref()
-                    .filter(|k2| k2.0.key == k.key && k2.1.elapsed() < MAX_REPEAT_TIME)
+                    // heuristic for guessing which presses are auto-repeating. not necessarily accurate
+                    .filter(|(last_data, last_instant)| {
+                        last_data.key() == k.key() && last_instant.elapsed() < MAX_REPEAT_TIME
+                    })
                     .is_some();
                     .is_some();
-                k.repeat = repeat;
-                let new = k.clone();
-                self.last_key_pressed = Some((new, Instant::now()));
+
+                if is_repeating {
+                    *k = KeyboardData::new(k.key(), k.code(), k.location(), true, k.modifiers());
+                }
+
+                self.last_key_pressed = Some((k.clone(), Instant::now()));
             }
             }
         }
         }
     }
     }
@@ -166,8 +174,10 @@ impl InnerInputState {
         let old_focus = self.focus_state.last_focused_id;
         let old_focus = self.focus_state.last_focused_id;
 
 
         evts.retain(|e| match &e.1 {
         evts.retain(|e| match &e.1 {
-            EventData::Keyboard(k) => match k.key_code {
-                KeyCode::Tab => !self.focus_state.progress(dom, !k.shift_key),
+            EventData::Keyboard(k) => match k.code() {
+                Code::Tab => !self
+                    .focus_state
+                    .progress(dom, !k.modifiers().contains(Modifiers::SHIFT)),
                 _ => true,
                 _ => true,
             },
             },
             _ => true,
             _ => true,
@@ -293,7 +303,10 @@ impl InnerInputState {
             // a mouse button is released if a button was down and is now not down
             // a mouse button is released if a button was down and is now not down
             let was_released = !(previous_buttons - mouse_data.held_buttons()).is_empty();
             let was_released = !(previous_buttons - mouse_data.held_buttons()).is_empty();
 
 
-            let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y);
+            let was_scrolled = self
+                .wheel
+                .as_ref()
+                .map_or(false, |data| !data.delta().is_zero());
             let wheel_data = &self.wheel;
             let wheel_data = &self.wheel;
 
 
             {
             {
@@ -457,7 +470,7 @@ impl InnerInputState {
             {
             {
                 // wheel
                 // wheel
                 if let Some(w) = wheel_data {
                 if let Some(w) = wheel_data {
-                    if wheel_delta != 0.0 {
+                    if was_scrolled {
                         let mut will_bubble = FxHashSet::default();
                         let mut will_bubble = FxHashSet::default();
                         for node in dom.get_listening_sorted("wheel") {
                         for node in dom.get_listening_sorted("wheel") {
                             let node_layout = get_abs_layout(node, dom, layout);
                             let node_layout = get_abs_layout(node, dom, layout);
@@ -723,13 +736,8 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
             };
             };
 
 
             let get_wheel_data = |up| {
             let get_wheel_data = |up| {
-                // from https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent
-                EventData::Wheel(WheelData {
-                    delta_mode: 0x01,
-                    delta_x: 0.0,
-                    delta_y: if up { -1.0 } else { 1.0 },
-                    delta_z: 0.0,
-                })
+                let y = if up { -1.0 } else { 1.0 };
+                EventData::Wheel(WheelData::new(WheelDelta::lines(0., y, 0.)))
             };
             };
 
 
             match m.kind {
             match m.kind {
@@ -748,147 +756,222 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
 }
 }
 
 
 fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
 fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
-    let (code, key_str);
-    let mut shift_key = event.modifiers.contains(KeyModifiers::SHIFT);
-    if let TermKeyCode::Char(c) = event.code {
-        code = match c {
+    let key = key_from_crossterm_key_code(event.code);
+    // crossterm does not provide code. we make a guess as to which key might have been pressed
+    // this is probably garbage if the user has a custom keyboard layout
+    let code = guess_code_from_crossterm_key_code(event.code)?;
+    let modifiers = modifiers_from_crossterm_modifiers(event.modifiers);
+
+    Some(EventData::Keyboard(KeyboardData::new(
+        key,
+        code,
+        Location::Standard,
+        false,
+        modifiers,
+    )))
+}
+
+/// The crossterm key_code nicely represents the meaning of the key and we can mostly convert it without any issues
+///
+/// Exceptions:
+/// BackTab is converted to Key::Tab, and Null is converted to Key::Unidentified
+fn key_from_crossterm_key_code(key_code: TermKeyCode) -> Key {
+    match key_code {
+        TermKeyCode::Backspace => Key::Backspace,
+        TermKeyCode::Enter => Key::Enter,
+        TermKeyCode::Left => Key::ArrowLeft,
+        TermKeyCode::Right => Key::ArrowRight,
+        TermKeyCode::Up => Key::ArrowUp,
+        TermKeyCode::Down => Key::ArrowDown,
+        TermKeyCode::Home => Key::Home,
+        TermKeyCode::End => Key::End,
+        TermKeyCode::PageUp => Key::PageUp,
+        TermKeyCode::PageDown => Key::PageDown,
+        TermKeyCode::Tab => Key::Tab,
+        // ? no corresponding Key
+        TermKeyCode::BackTab => Key::Tab,
+        TermKeyCode::Delete => Key::Delete,
+        TermKeyCode::Insert => Key::Insert,
+        TermKeyCode::F(1) => Key::F1,
+        TermKeyCode::F(2) => Key::F2,
+        TermKeyCode::F(3) => Key::F3,
+        TermKeyCode::F(4) => Key::F4,
+        TermKeyCode::F(5) => Key::F5,
+        TermKeyCode::F(6) => Key::F6,
+        TermKeyCode::F(7) => Key::F7,
+        TermKeyCode::F(8) => Key::F8,
+        TermKeyCode::F(9) => Key::F9,
+        TermKeyCode::F(10) => Key::F10,
+        TermKeyCode::F(11) => Key::F11,
+        TermKeyCode::F(12) => Key::F12,
+        TermKeyCode::F(13) => Key::F13,
+        TermKeyCode::F(14) => Key::F14,
+        TermKeyCode::F(15) => Key::F15,
+        TermKeyCode::F(16) => Key::F16,
+        TermKeyCode::F(17) => Key::F17,
+        TermKeyCode::F(18) => Key::F18,
+        TermKeyCode::F(19) => Key::F19,
+        TermKeyCode::F(20) => Key::F20,
+        TermKeyCode::F(21) => Key::F21,
+        TermKeyCode::F(22) => Key::F22,
+        TermKeyCode::F(23) => Key::F23,
+        TermKeyCode::F(24) => Key::F24,
+        TermKeyCode::F(other) => {
+            panic!("Unexpected function key: {other:?}")
+        }
+        TermKeyCode::Char(c) => Key::Character(c.to_string()),
+        TermKeyCode::Null => Key::Unidentified,
+        TermKeyCode::Esc => Key::Escape,
+    }
+}
+
+// Crossterm does not provide a way to get the `code` (physical key on keyboard)
+// So we make a guess based on their `key_code`, but this is probably going to break on anything other than a very standard european keyboard
+// It may look fine, but it's a horrible hack. But there's nothing better we can do.
+fn guess_code_from_crossterm_key_code(key_code: TermKeyCode) -> Option<Code> {
+    let code = match key_code {
+        TermKeyCode::Backspace => Code::Backspace,
+        TermKeyCode::Enter => Code::Enter,
+        TermKeyCode::Left => Code::ArrowLeft,
+        TermKeyCode::Right => Code::ArrowRight,
+        TermKeyCode::Up => Code::ArrowUp,
+        TermKeyCode::Down => Code::ArrowDown,
+        TermKeyCode::Home => Code::Home,
+        TermKeyCode::End => Code::End,
+        TermKeyCode::PageUp => Code::PageUp,
+        TermKeyCode::PageDown => Code::PageDown,
+        TermKeyCode::Tab => Code::Tab,
+        // ? Apparently you get BackTab by pressing Tab
+        TermKeyCode::BackTab => Code::Tab,
+        TermKeyCode::Delete => Code::Delete,
+        TermKeyCode::Insert => Code::Insert,
+        TermKeyCode::F(1) => Code::F1,
+        TermKeyCode::F(2) => Code::F2,
+        TermKeyCode::F(3) => Code::F3,
+        TermKeyCode::F(4) => Code::F4,
+        TermKeyCode::F(5) => Code::F5,
+        TermKeyCode::F(6) => Code::F6,
+        TermKeyCode::F(7) => Code::F7,
+        TermKeyCode::F(8) => Code::F8,
+        TermKeyCode::F(9) => Code::F9,
+        TermKeyCode::F(10) => Code::F10,
+        TermKeyCode::F(11) => Code::F11,
+        TermKeyCode::F(12) => Code::F12,
+        TermKeyCode::F(13) => Code::F13,
+        TermKeyCode::F(14) => Code::F14,
+        TermKeyCode::F(15) => Code::F15,
+        TermKeyCode::F(16) => Code::F16,
+        TermKeyCode::F(17) => Code::F17,
+        TermKeyCode::F(18) => Code::F18,
+        TermKeyCode::F(19) => Code::F19,
+        TermKeyCode::F(20) => Code::F20,
+        TermKeyCode::F(21) => Code::F21,
+        TermKeyCode::F(22) => Code::F22,
+        TermKeyCode::F(23) => Code::F23,
+        TermKeyCode::F(24) => Code::F24,
+        TermKeyCode::F(other) => {
+            panic!("Unexpected function key: {other:?}")
+        }
+        // this is a horrible way for crossterm to represent keys but we have to deal with it
+        TermKeyCode::Char(c) => match c {
             'A'..='Z' | 'a'..='z' => match c.to_ascii_uppercase() {
             'A'..='Z' | 'a'..='z' => match c.to_ascii_uppercase() {
-                'A' => KeyCode::A,
-                'B' => KeyCode::B,
-                'C' => KeyCode::C,
-                'D' => KeyCode::D,
-                'E' => KeyCode::E,
-                'F' => KeyCode::F,
-                'G' => KeyCode::G,
-                'H' => KeyCode::H,
-                'I' => KeyCode::I,
-                'J' => KeyCode::J,
-                'K' => KeyCode::K,
-                'L' => KeyCode::L,
-                'M' => KeyCode::M,
-                'N' => KeyCode::N,
-                'O' => KeyCode::O,
-                'P' => KeyCode::P,
-                'Q' => KeyCode::Q,
-                'R' => KeyCode::R,
-                'S' => KeyCode::S,
-                'T' => KeyCode::T,
-                'U' => KeyCode::U,
-                'V' => KeyCode::V,
-                'W' => KeyCode::W,
-                'X' => KeyCode::X,
-                'Y' => KeyCode::Y,
-                'Z' => KeyCode::Z,
-                _ => return None,
-            },
-            ' ' => KeyCode::Space,
-            '[' => KeyCode::OpenBracket,
-            '{' => KeyCode::OpenBracket,
-            ']' => KeyCode::CloseBraket,
-            '}' => KeyCode::CloseBraket,
-            ';' => KeyCode::Semicolon,
-            ':' => KeyCode::Semicolon,
-            ',' => KeyCode::Comma,
-            '<' => KeyCode::Comma,
-            '.' => KeyCode::Period,
-            '>' => KeyCode::Period,
-            '1' => KeyCode::Num1,
-            '2' => KeyCode::Num2,
-            '3' => KeyCode::Num3,
-            '4' => KeyCode::Num4,
-            '5' => KeyCode::Num5,
-            '6' => KeyCode::Num6,
-            '7' => KeyCode::Num7,
-            '8' => KeyCode::Num8,
-            '9' => KeyCode::Num9,
-            '0' => KeyCode::Num0,
-            '!' => KeyCode::Num1,
-            '@' => KeyCode::Num2,
-            '#' => KeyCode::Num3,
-            '$' => KeyCode::Num4,
-            '%' => KeyCode::Num5,
-            '^' => KeyCode::Num6,
-            '&' => KeyCode::Num7,
-            '*' => KeyCode::Num8,
-            '(' => KeyCode::Num9,
-            ')' => KeyCode::Num0,
-            // numpad charicter are ambiguous to tui
-            // '*' => KeyCode::Multiply,
-            // '/' => KeyCode::Divide,
-            // '-' => KeyCode::Subtract,
-            // '+' => KeyCode::Add,
-            '+' => KeyCode::EqualSign,
-            '-' => KeyCode::Dash,
-            '_' => KeyCode::Dash,
-            '\'' => KeyCode::SingleQuote,
-            '"' => KeyCode::SingleQuote,
-            '\\' => KeyCode::BackSlash,
-            '|' => KeyCode::BackSlash,
-            '/' => KeyCode::ForwardSlash,
-            '?' => KeyCode::ForwardSlash,
-            '=' => KeyCode::EqualSign,
-            '`' => KeyCode::GraveAccent,
-            '~' => KeyCode::GraveAccent,
-            _ => return None,
-        };
-        key_str = c.to_string();
-    } else {
-        code = match event.code {
-            TermKeyCode::Esc => KeyCode::Escape,
-            TermKeyCode::Backspace => KeyCode::Backspace,
-            TermKeyCode::Enter => KeyCode::Enter,
-            TermKeyCode::Left => KeyCode::LeftArrow,
-            TermKeyCode::Right => KeyCode::RightArrow,
-            TermKeyCode::Up => KeyCode::UpArrow,
-            TermKeyCode::Down => KeyCode::DownArrow,
-            TermKeyCode::Home => KeyCode::Home,
-            TermKeyCode::End => KeyCode::End,
-            TermKeyCode::PageUp => KeyCode::PageUp,
-            TermKeyCode::PageDown => KeyCode::PageDown,
-            TermKeyCode::Tab => KeyCode::Tab,
-            TermKeyCode::Delete => KeyCode::Delete,
-            TermKeyCode::Insert => KeyCode::Insert,
-            TermKeyCode::F(fn_num) => match fn_num {
-                1 => KeyCode::F1,
-                2 => KeyCode::F2,
-                3 => KeyCode::F3,
-                4 => KeyCode::F4,
-                5 => KeyCode::F5,
-                6 => KeyCode::F6,
-                7 => KeyCode::F7,
-                8 => KeyCode::F8,
-                9 => KeyCode::F9,
-                10 => KeyCode::F10,
-                11 => KeyCode::F11,
-                12 => KeyCode::F12,
-                _ => return None,
+                'A' => Code::KeyA,
+                'B' => Code::KeyB,
+                'C' => Code::KeyC,
+                'D' => Code::KeyD,
+                'E' => Code::KeyE,
+                'F' => Code::KeyF,
+                'G' => Code::KeyG,
+                'H' => Code::KeyH,
+                'I' => Code::KeyI,
+                'J' => Code::KeyJ,
+                'K' => Code::KeyK,
+                'L' => Code::KeyL,
+                'M' => Code::KeyM,
+                'N' => Code::KeyN,
+                'O' => Code::KeyO,
+                'P' => Code::KeyP,
+                'Q' => Code::KeyQ,
+                'R' => Code::KeyR,
+                'S' => Code::KeyS,
+                'T' => Code::KeyT,
+                'U' => Code::KeyU,
+                'V' => Code::KeyV,
+                'W' => Code::KeyW,
+                'X' => Code::KeyX,
+                'Y' => Code::KeyY,
+                'Z' => Code::KeyZ,
+                _ => unreachable!("Exhaustively checked all characters in range A..Z"),
             },
             },
-            // backtab is Shift + Tab
-            TermKeyCode::BackTab => {
-                shift_key = true;
-                KeyCode::Tab
-            }
-            TermKeyCode::Null => return None,
+            ' ' => Code::Space,
+            '[' | '{' => Code::BracketLeft,
+            ']' | '}' => Code::BracketRight,
+            ';' => Code::Semicolon,
+            ':' => Code::Semicolon,
+            ',' => Code::Comma,
+            '<' => Code::Comma,
+            '.' => Code::Period,
+            '>' => Code::Period,
+            '1' => Code::Digit1,
+            '2' => Code::Digit2,
+            '3' => Code::Digit3,
+            '4' => Code::Digit4,
+            '5' => Code::Digit5,
+            '6' => Code::Digit6,
+            '7' => Code::Digit7,
+            '8' => Code::Digit8,
+            '9' => Code::Digit9,
+            '0' => Code::Digit0,
+            '!' => Code::Digit1,
+            '@' => Code::Digit2,
+            '#' => Code::Digit3,
+            '$' => Code::Digit4,
+            '%' => Code::Digit5,
+            '^' => Code::Digit6,
+            '&' => Code::Digit7,
+            '*' => Code::Digit8,
+            '(' => Code::Digit9,
+            ')' => Code::Digit0,
+            // numpad characters are ambiguous; we don't know which key was really pressed
+            // it could be also:
+            // '*' => Code::Multiply,
+            // '/' => Code::Divide,
+            // '-' => Code::Subtract,
+            // '+' => Code::Add,
+            '+' => Code::Equal,
+            '-' | '_' => Code::Minus,
+            '\'' => Code::Quote,
+            '"' => Code::Quote,
+            '\\' => Code::Backslash,
+            '|' => Code::Backslash,
+            '/' => Code::Slash,
+            '?' => Code::Slash,
+            '=' => Code::Equal,
+            '`' => Code::Backquote,
+            '~' => Code::Backquote,
             _ => return None,
             _ => return None,
-        };
-        key_str = if let KeyCode::BackSlash = code {
-            "\\".to_string()
-        } else {
-            format!("{code:?}")
-        }
+        },
+        TermKeyCode::Null => return None,
+        TermKeyCode::Esc => Code::Escape,
     };
     };
-    // from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
-    Some(EventData::Keyboard(KeyboardData {
-        char_code: code.raw_code(),
-        key: key_str,
-        key_code: code,
-        alt_key: event.modifiers.contains(KeyModifiers::ALT),
-        ctrl_key: event.modifiers.contains(KeyModifiers::CONTROL),
-        meta_key: false,
-        shift_key,
-        locale: Default::default(),
-        location: 0x00,
-        repeat: Default::default(),
-        which: Default::default(),
-    }))
+
+    Some(code)
+}
+
+fn modifiers_from_crossterm_modifiers(src: KeyModifiers) -> Modifiers {
+    let mut modifiers = Modifiers::empty();
+
+    if src.contains(KeyModifiers::SHIFT) {
+        modifiers.insert(Modifiers::SHIFT);
+    }
+
+    if src.contains(KeyModifiers::ALT) {
+        modifiers.insert(Modifiers::ALT);
+    }
+
+    if src.contains(KeyModifiers::CONTROL) {
+        modifiers.insert(Modifiers::CONTROL);
+    }
+
+    modifiers
 }
 }

+ 3 - 2
packages/tui/tests/events.rs

@@ -1,5 +1,6 @@
 use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent};
 use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent};
 use dioxus::prelude::*;
 use dioxus::prelude::*;
+use dioxus_html::input_data::keyboard_types::Code;
 use dioxus_tui::TuiContext;
 use dioxus_tui::TuiContext;
 use std::future::Future;
 use std::future::Future;
 use std::pin::Pin;
 use std::pin::Pin;
@@ -56,7 +57,7 @@ fn key_down() {
                 width: "100%",
                 width: "100%",
                 height: "100%",
                 height: "100%",
                 onkeydown: move |evt| {
                 onkeydown: move |evt| {
-                    assert_eq!(evt.data.key_code, dioxus_html::KeyCode::A);
+                    assert_eq!(evt.data.code(), Code::KeyA);
                     tui_ctx.quit();
                     tui_ctx.quit();
                 },
                 },
             }
             }
@@ -286,7 +287,7 @@ fn wheel() {
                 width: "100%",
                 width: "100%",
                 height: "100%",
                 height: "100%",
                 onwheel: move |evt| {
                 onwheel: move |evt| {
-                    assert!(evt.data.delta_y > 0.0);
+                    assert!(evt.data.delta().strip_units().y > 0.0);
                     tui_ctx.quit();
                     tui_ctx.quit();
                 },
                 },
             }
             }

+ 52 - 46
packages/web/src/lib.rs

@@ -62,6 +62,7 @@ use dioxus_core::prelude::Component;
 use dioxus_core::SchedulerMsg;
 use dioxus_core::SchedulerMsg;
 use dioxus_core::VirtualDom;
 use dioxus_core::VirtualDom;
 use futures_util::FutureExt;
 use futures_util::FutureExt;
+use web_sys::console;
 
 
 mod cache;
 mod cache;
 mod cfg;
 mod cfg;
@@ -172,50 +173,6 @@ pub fn launch_with_props<T>(
 pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T, cfg: WebConfig) {
 pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T, cfg: WebConfig) {
     let mut dom = VirtualDom::new_with_props(root, root_props);
     let mut dom = VirtualDom::new_with_props(root, root_props);
 
 
-    for s in crate::cache::BUILTIN_INTERNED_STRINGS {
-        wasm_bindgen::intern(s);
-    }
-    for s in &cfg.cached_strings {
-        wasm_bindgen::intern(s);
-    }
-
-    let tasks = dom.get_scheduler_channel();
-
-    let sender_callback: Rc<dyn Fn(SchedulerMsg)> =
-        Rc::new(move |event| tasks.unbounded_send(event).unwrap());
-
-    let should_hydrate = cfg.hydrate;
-
-    let mut websys_dom = dom::WebsysDom::new(cfg, sender_callback);
-
-    log::trace!("rebuilding app");
-
-    if should_hydrate {
-        // todo: we need to split rebuild and initialize into two phases
-        // it's a waste to produce edits just to get the vdom loaded
-        let _ = dom.rebuild();
-
-        if let Err(err) = websys_dom.rehydrate(&dom) {
-            log::error!(
-                "Rehydration failed {:?}. Rebuild DOM into element from scratch",
-                &err
-            );
-
-            websys_dom.root.set_text_content(None);
-
-            // errrrr we should split rebuild into two phases
-            // one that initializes things and one that produces edits
-            let edits = dom.rebuild();
-
-            websys_dom.apply_edits(edits.edits);
-        }
-    } else {
-        let edits = dom.rebuild();
-        websys_dom.apply_edits(edits.edits);
-    }
-
-    let mut work_loop = ric_raf::RafLoop::new();
-
     #[cfg(feature = "hot-reload")]
     #[cfg(feature = "hot-reload")]
     {
     {
         use dioxus_rsx_interpreter::error::Error;
         use dioxus_rsx_interpreter::error::Error;
@@ -274,12 +231,61 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
         // forward stream to the websocket
         // forward stream to the websocket
         dom.base_scope().spawn_forever(async move {
         dom.base_scope().spawn_forever(async move {
             while let Some(err) = error_channel_receiver.next().await {
             while let Some(err) = error_channel_receiver.next().await {
-                ws.send_with_str(serde_json::to_string(&err).unwrap().as_str())
-                    .unwrap();
+                if ws.ready_state() == WebSocket::OPEN {
+                    ws.send_with_str(serde_json::to_string(&err).unwrap().as_str())
+                        .unwrap();
+                } else {
+                    console::warn_1(&"WebSocket is not open, cannot send error. Run with dioxus serve --hot-reload to enable hot reloading.".into());
+                    panic!("{}", err);
+                }
             }
             }
         });
         });
     }
     }
 
 
+    for s in crate::cache::BUILTIN_INTERNED_STRINGS {
+        wasm_bindgen::intern(s);
+    }
+    for s in &cfg.cached_strings {
+        wasm_bindgen::intern(s);
+    }
+
+    let tasks = dom.get_scheduler_channel();
+
+    let sender_callback: Rc<dyn Fn(SchedulerMsg)> =
+        Rc::new(move |event| tasks.unbounded_send(event).unwrap());
+
+    let should_hydrate = cfg.hydrate;
+
+    let mut websys_dom = dom::WebsysDom::new(cfg, sender_callback);
+
+    log::trace!("rebuilding app");
+
+    if should_hydrate {
+        // todo: we need to split rebuild and initialize into two phases
+        // it's a waste to produce edits just to get the vdom loaded
+        let _ = dom.rebuild();
+
+        if let Err(err) = websys_dom.rehydrate(&dom) {
+            log::error!(
+                "Rehydration failed {:?}. Rebuild DOM into element from scratch",
+                &err
+            );
+
+            websys_dom.root.set_text_content(None);
+
+            // errrrr we should split rebuild into two phases
+            // one that initializes things and one that produces edits
+            let edits = dom.rebuild();
+
+            websys_dom.apply_edits(edits.edits);
+        }
+    } else {
+        let edits = dom.rebuild();
+        websys_dom.apply_edits(edits.edits);
+    }
+
+    let mut work_loop = ric_raf::RafLoop::new();
+
     loop {
     loop {
         log::trace!("waiting for work");
         log::trace!("waiting for work");
         // if virtualdom has nothing, wait for it to have something before requesting idle time
         // if virtualdom has nothing, wait for it to have something before requesting idle time

+ 0 - 1
packages/web/src/olddom.rs

@@ -521,7 +521,6 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send
                 key: evt.key(),
                 key: evt.key(),
                 key_code: KeyCode::from_raw_code(evt.key_code() as u8),
                 key_code: KeyCode::from_raw_code(evt.key_code() as u8),
                 ctrl_key: evt.ctrl_key(),
                 ctrl_key: evt.ctrl_key(),
-                locale: "not implemented".to_string(),
                 location: evt.location() as usize,
                 location: evt.location() as usize,
                 meta_key: evt.meta_key(),
                 meta_key: evt.meta_key(),
                 repeat: evt.repeat(),
                 repeat: evt.repeat(),