[
  {
    "path": ".gitignore",
    "content": "/target\n/Cargo.lock\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nmembers = [\n    \"squark\",\n    \"squark-macros\",\n    \"squark-web\",\n]\n"
  },
  {
    "path": "LICENCE",
    "content": "            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n                    Version 2, December 2004\n\n Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>\n\n Everyone is permitted to copy and distribute verbatim or modified\n copies of this license document, and changing it is allowed as long\n as the name is changed.\n\n            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. You just DO WHAT THE FUCK YOU WANT TO.\n"
  },
  {
    "path": "README.md",
    "content": "# squark\n\nRust frontend framework, for web browser and more.\n\n**Currently, we depend on `nightly` channel**\n\n## Design\n\n* Separating runtime definition and implemention\n  + `squark` crate has no dependency for specific platform\n* Architecture inspired from [Elm](https://elm-lang.org/) and [HyperApp](https://github.com/hyperapp/hyperapp/)\n  + Simplicy\n  + Elegant\n* Supporting futures-0.1\n  + reducer can emit task for async work such as fetch resource\n\n## crates\n\n### squark\n\n[![crates.io](https://img.shields.io/crates/v/squark.svg)](https://crates.io/crates/squark)\n[![docs.rs](https://docs.rs/squark/badge.svg)](https://docs.rs/squark/*/squark/)\n\nCore crate.\n\n* Pure Rust virtual DOM implemention\n* Definition of GUI application\n* Definition of runtime to handle diffirence of virtual DOM\n\n### squark-macros\n\n[![crates.io](https://img.shields.io/crates/v/squark-macros.svg)](https://crates.io/crates/squark-macros)\n[![docs.rs](https://docs.rs/squark-macros/badge.svg)](https://docs.rs/squark-macros/*/squark_macros/)\n\nIt provides macro like JSX for helping writing view.  \nVery thanks to [pest](https://github.com/pest-parser/pest) parser.\n\n#### Syntax\n\n```   \nview! {\n    <button class=\"some-class\" onclick={ |_| Some(Action::Submit) }>\n        Button!\n    </button>\n}\n```\n\nWe can generate native Rust expression at compile-time.\n\n\n### squark-web\n\n[![crates.io](https://img.shields.io/crates/v/squark-web.svg)](https://crates.io/crates/squark-web)\n[![docs.rs](https://docs.rs/squark-web/badge.svg)](https://docs.rs/squark-web/*/squark_web/)\n\nRuntime implemention for web browser with usinng [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen/).\n\nHere is full example of counter app!\n\n```rust\n#![feature(proc_macro_hygiene)]\n\nextern crate squark;\nextern crate squark_macros;\nextern crate squark_web;\nextern crate wasm_bindgen;\nextern crate web_sys;\n\nuse squark::{App, Runtime, View, Task};\nuse squark_macros::view;\nuse squark_web::WebRuntime;\nuse wasm_bindgen::prelude::*;\nuse web_sys::window;\n\n#[derive(Clone, Debug, PartialEq)]\nstruct State {\n    count: isize,\n}\n\nimpl State {\n    pub fn new() -> State {\n        State { count: 0 }\n    }\n}\n\n#[derive(Clone, Debug)]\nenum Action {\n    ChangeCount(isize),\n}\n\n#[derive(Clone, Debug)]\nstruct CounterApp;\nimpl App for CounterApp {\n    type State = State;\n    type Action = Action;\n\n    fn reducer(&self, mut state: State, action: Action) -> (State, Task<Action>) {\n        match action {\n            Action::ChangeCount(c) => {\n                state.count = c;\n            }\n        };\n        (state, Task::empty())\n    }\n\n    fn view(&self, state: State) -> View<Action> {\n        let count = state.count;\n        view! {\n            <div>\n                { count.to_string() }\n                <button onclick={ move |_| Some(Action::ChangeCount(count.clone() + 1)) }>\n                    increment\n                </button>\n                <button onclick={ move |_| Some(Action::ChangeCount(count - 1)) }>\n                    decrement\n                </button>\n            </div>\n        }\n    }\n}\n\nimpl Default for CounterApp {\n    fn default() -> CounterApp {\n        CounterApp\n    }\n}\n\n#[wasm_bindgen]\npub fn run() {\n    WebRuntime::<CounterApp>::new(\n        window()\n            .unwrap()\n            .document()\n            .expect(\"Failed to get document\")\n            .query_selector(\"body\")\n            .unwrap()\n            .unwrap(),\n        State::new(),\n    )\n    .run();\n}\n```\n\nProject dir is located at [examples/counter](./examples/counter).\n\nThere are some other examples available on [examples](./examples), most of them use [rust-webpack-template](https://github.com/rustwasm/rust-webpack-template).  \nTodoMVC is working on [https://rail44.github.io/squark/](https://rail44.github.io/squark/).\n"
  },
  {
    "path": "examples/counter/.gitignore",
    "content": "dist\nnode_modules\ncrate/pkg\ncrate/target\ncrate/wasm-pack.log\n"
  },
  {
    "path": "examples/counter/README.md",
    "content": "Just run\n\n```bash\nnpm run start\n```\n\ndev server will come with hot reloading.\n"
  },
  {
    "path": "examples/counter/crate/.crates.toml",
    "content": "[v1]\n\"wasm-bindgen-cli 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)\" = [\"wasm-bindgen\", \"wasm-bindgen-test-runner\", \"wasm2es6js\"]\n"
  },
  {
    "path": "examples/counter/crate/.gitignore",
    "content": "\n/target/\n**/*.rs.bk\nCargo.lock\n"
  },
  {
    "path": "examples/counter/crate/Cargo.toml",
    "content": "[package]\nname = \"counter\"\nversion = \"0.1.0\"\nauthors = [\"Satoshi Amemiya <amemiya@protonmail.com>\"]\n\n[workspace]\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nsquark = { \"path\" = \"../../../squark\" }\nsquark-macros = { \"path\" = \"../../../squark-macros\" }\nsquark-web = { \"path\" = \"../../../squark-web\" }\nserde_json = \"1.0.13\"\nwasm-bindgen = \"0.2.19\"\nfutures = \"0.1.25\"\njs-sys = \"0.3.6\"\nwasm-bindgen-futures = \"0.3.6\"\n\n[dependencies.web-sys]\nversion = \"0.3.2\"\nfeatures = [\n  'Document',\n  'Window',\n]\n"
  },
  {
    "path": "examples/counter/crate/src/lib.rs",
    "content": "#![feature(proc_macro_hygiene)]\n\nextern crate squark;\nextern crate squark_macros;\nextern crate squark_web;\nextern crate wasm_bindgen;\nextern crate web_sys;\n\nuse squark::{App, Runtime, View, Task};\nuse squark_macros::view;\nuse squark_web::WebRuntime;\nuse wasm_bindgen::prelude::*;\nuse web_sys::window;\n\n#[derive(Clone, Debug, PartialEq)]\nstruct State {\n    count: isize,\n}\n\nimpl State {\n    pub fn new() -> State {\n        State { count: 0 }\n    }\n}\n\n#[derive(Clone, Debug)]\nenum Action {\n    ChangeCount(isize),\n}\n\n#[derive(Clone, Debug)]\nstruct CounterApp;\nimpl App for CounterApp {\n    type State = State;\n    type Action = Action;\n\n    fn reducer(&self, mut state: State, action: Action) -> (State, Task<Action>) {\n        match action {\n            Action::ChangeCount(c) => {\n                state.count = c;\n            }\n        };\n        (state, Task::empty())\n    }\n\n    fn view(&self, state: State) -> View<Action> {\n        let count = state.count;\n        view! {\n            <div>\n                { count.to_string() }\n                <button onclick={ move |_| Some(Action::ChangeCount(count.clone() + 1)) }>\n                    increment\n                </button>\n                <button onclick={ move |_| Some(Action::ChangeCount(count - 1)) }>\n                    decrement\n                </button>\n            </div>\n        }\n    }\n}\n\nimpl Default for CounterApp {\n    fn default() -> CounterApp {\n        CounterApp\n    }\n}\n\n#[wasm_bindgen]\npub fn run() {\n    WebRuntime::<CounterApp>::new(\n        window()\n            .unwrap()\n            .document()\n            .expect(\"Failed to get document\")\n            .query_selector(\"body\")\n            .unwrap()\n            .unwrap(),\n        State::new(),\n    )\n    .run();\n}\n"
  },
  {
    "path": "examples/counter/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"utf-8\">\n    </head>\n    <body>\n        <script src='./dist/bundle.js'></script>\n    </body>\n</html>\n"
  },
  {
    "path": "examples/counter/js/index.js",
    "content": "import(\"../crate/pkg\").then(module => {\n  module.run();\n});\n"
  },
  {
    "path": "examples/counter/package.json",
    "content": "{\n  \"scripts\": {\n    \"start\": \"webpack-dev-server -d\",\n    \"build\": \"webpack --mode none\"\n  },\n  \"devDependencies\": {\n    \"@wasm-tool/wasm-pack-plugin\": \"^0.2.0\",\n    \"webpack\": \"^4.28.1\",\n    \"webpack-cli\": \"^3.2.1\",\n    \"webpack-dev-server\": \"^3.1.14\"\n  }\n}\n"
  },
  {
    "path": "examples/counter/webpack.config.js",
    "content": "const path = require(\"path\");\nconst dist = path.resolve(__dirname, \"dist\");\nconst WasmPackPlugin = require(\"@wasm-tool/wasm-pack-plugin\");\n\nmodule.exports = {\n  entry: \"./js/index.js\",\n  output: {\n    publicPath: \"dist/\",\n    path: dist,\n    filename: \"bundle.js\"\n  },\n  plugins: [\n    new WasmPackPlugin({\n      crateDirectory: path.resolve(__dirname, \"crate\"),\n    }),\n  ],\n  devServer: {\n    host: '0.0.0.0',\n  },\n};\n"
  },
  {
    "path": "examples/todomvc/.gitignore",
    "content": "dist\nnode_modules\ncrate/pkg\ncrate/target\ncrate/wasm-pack.log\n"
  },
  {
    "path": "examples/todomvc/README.md",
    "content": "Just run\n\n```bash\nnpm run start\n```\n\ndev server will come with hot reloading.\n"
  },
  {
    "path": "examples/todomvc/crate/.crates.toml",
    "content": "[v1]\n\"wasm-bindgen-cli 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)\" = [\"wasm-bindgen\", \"wasm-bindgen-test-runner\", \"wasm2es6js\"]\n"
  },
  {
    "path": "examples/todomvc/crate/.gitignore",
    "content": "\n/target/\n**/*.rs.bk\nCargo.lock\n"
  },
  {
    "path": "examples/todomvc/crate/Cargo.toml",
    "content": "[package]\nname = \"todomvc\"\nversion = \"0.1.0\"\nauthors = [\"Satoshi Amemiya <amemiya@protonmail.com>\"]\n\n[workspace]\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nsquark = { \"path\" = \"../../../squark\" }\nsquark-macros = { \"path\" = \"../../../squark-macros\" }\nsquark-web = { \"path\" = \"../../../squark-web\" }\nserde_json = \"1.0.13\"\nwasm-bindgen = \"0.2.29\"\n\n[dependencies.web-sys]\nversion = \"0.3.2\"\nfeatures = [\n  'Document',\n  'Window',\n]\n"
  },
  {
    "path": "examples/todomvc/crate/src/lib.rs",
    "content": "#![feature(proc_macro_hygiene)]\n\nextern crate serde_json;\nextern crate squark;\nextern crate squark_macros;\nextern crate squark_web;\nextern crate wasm_bindgen;\nextern crate web_sys;\n\nuse squark::{uuid, App, Child, HandlerArg, Runtime, View, Task};\nuse squark_macros::view;\nuse squark_web::WebRuntime;\nuse std::iter::FromIterator;\nuse wasm_bindgen::prelude::*;\nuse web_sys::window;\n\n#[derive(Clone, Hash, Debug, PartialEq)]\nenum Visibility {\n    All,\n    Active,\n    Completed,\n}\n\nimpl ToString for Visibility {\n    fn to_string(&self) -> String {\n        match self {\n            Visibility::All => \"All\".to_string(),\n            Visibility::Active => \"Active\".to_string(),\n            Visibility::Completed => \"Completed\".to_string(),\n        }\n    }\n}\n\nimpl Visibility {\n    pub fn view(&self, selected: bool) -> View<Action> {\n        let this = self.clone();\n        let class = if selected { \"selected\" } else { \"\" };\n        view! {\n            <li>\n                <a class={ class } style=\"cursor: pointer\" onclick={ move |_| { Some(Action::ChangeVisibility(this.clone())) } }>\n                    { self.to_string() }\n                </a>\n            </li>\n        }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq)]\nstruct Entry {\n    id: String,\n    description: String,\n    completed: bool,\n}\n\nimpl Entry {\n    pub fn new(description: String) -> Entry {\n        Entry {\n            id: uuid(),\n            description,\n            completed: false,\n        }\n    }\n\n    pub fn should_display(&self, visibility: &Visibility) -> bool {\n        match visibility {\n            Visibility::All => true,\n            Visibility::Active => !self.completed,\n            Visibility::Completed => self.completed,\n        }\n    }\n\n    pub fn view(&self, i: usize, editing: bool) -> View<Action> {\n        let completed = self.completed;\n        let mut class = vec![];\n        if completed {\n            class.push(\"completed\");\n        }\n        if editing {\n            class.push(\"editing\");\n        }\n\n        view! {\n            <li key={ self.id.clone() } class={ class.join(\" \") }>\n                {\n                    if editing {\n                        let id = format!(\"edit-{}\", i);\n                        view! {\n                            <input\n                                id={ id.clone() }\n                                class=\"edit\"\n                                type=\"text\"\n                                value={ self.description.clone() }\n                                oninput={ |v| match v {\n                                    HandlerArg::String(v) => Some(Action::UpdateEntry(v)),\n                                    _ => None,\n                                } }\n                                onkeydown={ |v| match v {\n                                    HandlerArg::String(ref v) if v.as_str() == \"Enter\" => {\n                                        Some(Action::EndEditing)\n                                    }\n                                    _ => None,\n                                } }\n                                onblur={ move |_| Some(Action::EndEditing) } />\n                        }\n                    } else {\n                        view! {\n                            <div class=\"view\">\n                                <input\n                                    class=\"toggle\"\n                                    type=\"checkbox\"\n                                    checked={ completed }\n                                    onclick={\n                                        move |_| {\n                                            Some(Action::Check(i, !completed))\n                                        }\n                                    }/>\n                                <label ondblclick={\n                                    move |_| { Some(Action::EditEntry(i))\n                                    }\n                                }>\n                                    { self.description.clone() }\n                                </label>\n                                <button class=\"destroy\" onclick={ move |_| { Some(Action::Remove(i)) } } />\n                            </div>\n                        }\n                    }\n                }\n            </li>\n        }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq)]\nstruct State {\n    entries: Vec<Entry>,\n    field: String,\n    editing: Option<usize>,\n    visibility: Visibility,\n}\n\nimpl State {\n    pub fn new() -> State {\n        State {\n            entries: vec![],\n            field: \"\".to_string(),\n            editing: None,\n            visibility: Visibility::All,\n        }\n    }\n\n    pub fn has_completed(&self) -> bool {\n        self.entries.iter().any(|e| e.completed)\n    }\n\n    pub fn not_completed_count(&self) -> usize {\n        self.entries.len() - self.completed_count()\n    }\n\n    pub fn completed_count(&self) -> usize {\n        self.entries.iter().filter(|e| e.completed).count()\n    }\n\n    pub fn is_all_completed(&self) -> bool {\n        self.completed_count() == self.entries.len()\n    }\n}\n\n#[derive(Clone, Debug)]\nenum Action {\n    UpdateField(String),\n    EditEntry(usize),\n    UpdateEntry(String),\n    EndEditing,\n    Add,\n    Remove(usize),\n    RemoveComplete,\n    Check(usize, bool),\n    CheckAll(bool),\n    ChangeVisibility(Visibility),\n}\n\nfn header_view(state: &State) -> View<Action> {\n    view! {\n        <header class=\"header\">\n            <h1>todos</h1>\n            <input\n                class=\"new-todo\"\n                placeholder=\"What needs to be done?\"\n                value={ state.field.clone() }\n                oninput={ |v| match v {\n                    HandlerArg::String(v) => Some(Action::UpdateField(v)),\n                    _ => None,\n                } }\n                onkeydown={ |v| match v {\n                    HandlerArg::String(ref v) if v.as_str() == \"Enter\" => Some(Action::Add),\n                    _ => None,\n                } } />\n        </header>\n    }\n}\n\nfn main_view(state: &State) -> View<Action> {\n    let is_all_completed = state.is_all_completed();\n    view! {\n        <section class=\"main\">\n            {\n                if state.entries.len() > 0 {\n                    view! {\n                        <span>\n                            <input\n                                class=\"toggle-all\"\n                                type=\"checkbox\"\n                                checked={ is_all_completed }\n                                onclick={\n                                    move |_| {\n                                        Some(Action::CheckAll(!is_all_completed))\n                                    }\n                                } />\n                        </span>\n                    }\n                } else {\n                    ().into()\n                }\n\n            }\n            <ul class=\"todo-list\" type=\"checkbox\">\n                {\n                    Child::from_iter(state\n                        .entries\n                        .iter()\n                        .enumerate()\n                        .filter(|&(_, e)| e.should_display(&state.visibility))\n                        .map(|(i, e)| {\n                            let is_editing = state.editing.as_ref().map_or(false, |at| &i == at);\n                            e.view(i, is_editing)\n                        }))\n                }\n            </ul>\n        </section>\n    }\n}\n\nfn footer_view(state: &State) -> View<Action> {\n    if state.entries.is_empty() {\n        return ().into();\n    }\n    view! {\n        <footer class=\"footer\">\n            <span class=\"todo-count\">\n                <strong>\n                    { state.not_completed_count().to_string() }\n                </strong>\n                item(s) left\n            </span>\n            <ul class=\"filters\">\n                {\n                    Child::from_iter(\n                        vec![Visibility::All, Visibility::Active, Visibility::Completed]\n                            .into_iter()\n                            .map(|v| v.view(v == state.visibility))\n                    )\n                }\n            </ul>\n            {\n                if state.has_completed() {\n                    view! {\n                        <button class=\"clear-completed\" onclick={ move |_| { Some(Action::RemoveComplete) } }>\n                            Clear completed\n                        </button>\n                    }\n                } else {\n                    ().into()\n                }\n            }\n        </footer>\n    }\n}\n\n#[derive(Clone, Debug)]\nstruct TodoApp;\nimpl App for TodoApp {\n    type State = State;\n    type Action = Action;\n\n    fn reducer(&self, mut state: State, action: Action) -> (State, Task<Action>) {\n        match action {\n            Action::Add => {\n                if state.field.as_str() != \"\" {\n                    let entry = Entry::new(state.field);\n                    state.entries.push(entry);\n                    state.field = \"\".to_string();\n                }\n            }\n            Action::UpdateField(s) => {\n                state.field = s;\n            }\n            Action::EndEditing => {\n                if let Some(i) = state.editing {\n                    if state.entries[i].description.as_str() == \"\" {\n                        state.entries.remove(i);\n                    }\n                    state.editing = None;\n                }\n            }\n            Action::UpdateEntry(s) => {\n                if let Some(i) = state.editing {\n                    state.entries[i].description = s;\n                }\n            }\n            Action::CheckAll(b) => {\n                for mut entry in &mut state.entries {\n                    entry.completed = b;\n                }\n            }\n            Action::Check(at, b) => {\n                state.entries[at].completed = b;\n            }\n            Action::Remove(at) => {\n                state.entries.remove(at);\n            }\n            Action::RemoveComplete => {\n                let entries = state.entries.drain(..).filter(|e| !e.completed).collect();\n                state.entries = entries;\n            }\n            Action::EditEntry(i) => {\n                state.editing = Some(i);\n            }\n            Action::ChangeVisibility(v) => {\n                state.visibility = v;\n            }\n        };\n        (state, Task::empty())\n    }\n\n    fn view(&self, state: State) -> View<Action> {\n        view! {\n            <div>\n                { header_view(&state) }\n                { main_view(&state) }\n                { footer_view(&state) }\n            </div>\n        }\n    }\n}\n\nimpl Default for TodoApp {\n    fn default() -> TodoApp {\n        TodoApp\n    }\n}\n\n#[wasm_bindgen]\npub fn run() {\n    WebRuntime::<TodoApp>::new(\n        window()\n            .unwrap()\n            .document()\n            .unwrap()\n            .query_selector(\"#container\")\n            .unwrap()\n            .unwrap(),\n        State::new(),\n    )\n    .run();\n}\n"
  },
  {
    "path": "examples/todomvc/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"utf-8\">\n        <title>squark-web • TodoMVC</title>\n        <link rel=\"stylesheet\" href=\"./styles.css\">\n    </head>\n    <body>\n        <div class=\"todomvc-wrapper\">\n            <section id=\"container\" class=\"todoapp\">\n            </section>\n            <footer class=\"info\">\n                <p>Double-click to edit a todo</p>\n                <p>Written by <a href=\"https://github.com/rail44/\" target=\"_blank\">Satoshi Amemiya</a></p>\n                <p>Part of <a href=\"http://todomvc.com/\" target=\"_blank\">TodoMVC</a></p>\n            </footer>\n        </div>\n        <script src='./dist/bundle.js'></script>\n    </body>\n</html>\n"
  },
  {
    "path": "examples/todomvc/js/index.js",
    "content": "import(\"../crate/pkg\").then(module => {\n  module.run();\n});\n"
  },
  {
    "path": "examples/todomvc/package.json",
    "content": "{\n  \"scripts\": {\n    \"start\": \"webpack-dev-server -d\",\n    \"build\": \"webpack --mode none\"\n  },\n  \"devDependencies\": {\n    \"@wasm-tool/wasm-pack-plugin\": \"^0.2.0\",\n    \"webpack\": \"^4.28.1\",\n    \"webpack-cli\": \"^3.2.1\",\n    \"webpack-dev-server\": \"^3.1.14\"\n  }\n}\n"
  },
  {
    "path": "examples/todomvc/styles.css",
    "content": "/* Source: https://github.com/evancz/elm-todomvc/blob/master/style.css */\n\nhtml,\nbody {\n    margin: 0;\n    padding: 0;\n}\n\n.todomvc-wrapper {\n    visibility: visible !important;\n}\n\nbutton {\n    margin: 0;\n    padding: 0;\n    border: 0;\n    background: none;\n    font-size: 100%;\n    vertical-align: baseline;\n    font-family: inherit;\n    font-weight: inherit;\n    color: inherit;\n    -webkit-appearance: none;\n    appearance: none;\n    -webkit-font-smoothing: antialiased;\n    -moz-font-smoothing: antialiased;\n    font-smoothing: antialiased;\n}\n\nbody {\n    font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;\n    line-height: 1.4em;\n    background: #f5f5f5;\n    color: #4d4d4d;\n    min-width: 230px;\n    max-width: 550px;\n    margin: 0 auto;\n    -webkit-font-smoothing: antialiased;\n    -moz-font-smoothing: antialiased;\n    font-smoothing: antialiased;\n    font-weight: 300;\n}\n\nbutton,\ninput[type=\"checkbox\"] {\n    outline: none;\n}\n\n.hidden {\n    display: none;\n}\n\n.todoapp {\n    background: #fff;\n    margin: 130px 0 40px 0;\n    position: relative;\n    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),\n                0 25px 50px 0 rgba(0, 0, 0, 0.1);\n}\n\n.todoapp input::-webkit-input-placeholder {\n    font-style: italic;\n    font-weight: 300;\n    color: #e6e6e6;\n}\n\n.todoapp input::-moz-placeholder {\n    font-style: italic;\n    font-weight: 300;\n    color: #e6e6e6;\n}\n\n.todoapp input::input-placeholder {\n    font-style: italic;\n    font-weight: 300;\n    color: #e6e6e6;\n}\n\n.todoapp h1 {\n    position: absolute;\n    top: -155px;\n    width: 100%;\n    font-size: 100px;\n    font-weight: 100;\n    text-align: center;\n    color: rgba(175, 47, 47, 0.15);\n    -webkit-text-rendering: optimizeLegibility;\n    -moz-text-rendering: optimizeLegibility;\n    text-rendering: optimizeLegibility;\n}\n\n.new-todo,\n.edit {\n    position: relative;\n    margin: 0;\n    width: 100%;\n    font-size: 24px;\n    font-family: inherit;\n    font-weight: inherit;\n    line-height: 1.4em;\n    border: 0;\n    outline: none;\n    color: inherit;\n    padding: 6px;\n    border: 1px solid #999;\n    box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);\n    box-sizing: border-box;\n    -webkit-font-smoothing: antialiased;\n    -moz-font-smoothing: antialiased;\n    font-smoothing: antialiased;\n}\n\n.new-todo {\n    padding: 16px 16px 16px 60px;\n    border: none;\n    background: rgba(0, 0, 0, 0.003);\n    box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);\n}\n\n.main {\n    position: relative;\n    z-index: 2;\n    border-top: 1px solid #e6e6e6;\n}\n\nlabel[for='toggle-all'] {\n    display: none;\n}\n\n.toggle-all {\n    position: absolute;\n    top: -55px;\n    left: -12px;\n    width: 60px;\n    height: 34px;\n    text-align: center;\n    border: none; /* Mobile Safari */\n}\n\n.toggle-all:before {\n    content: '❯';\n    font-size: 22px;\n    color: #e6e6e6;\n    padding: 10px 27px 10px 27px;\n}\n\n.toggle-all:checked:before {\n    color: #737373;\n}\n\n.todo-list {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n}\n\n.todo-list li {\n    position: relative;\n    font-size: 24px;\n    border-bottom: 1px solid #ededed;\n}\n\n.todo-list li:last-child {\n    border-bottom: none;\n}\n\n.todo-list li.editing {\n    border-bottom: none;\n    padding: 0;\n}\n\n.todo-list li.editing .edit {\n    display: block;\n    width: 506px;\n    padding: 13px 17px 12px 17px;\n    margin: 0 0 0 43px;\n}\n\n.todo-list li.editing .view {\n    display: none;\n}\n\n.todo-list li .toggle {\n    text-align: center;\n    width: 40px;\n    /* auto, since non-WebKit browsers doesn't support input styling */\n    height: auto;\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    margin: auto 0;\n    border: none; /* Mobile Safari */\n    -webkit-appearance: none;\n    appearance: none;\n}\n\n.todo-list li .toggle:after {\n    content: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"40\" viewBox=\"-10 -18 100 135\"><circle cx=\"50\" cy=\"50\" r=\"50\" fill=\"none\" stroke=\"#ededed\" stroke-width=\"3\"/></svg>');\n}\n\n.todo-list li .toggle:checked:after {\n    content: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"40\" viewBox=\"-10 -18 100 135\"><circle cx=\"50\" cy=\"50\" r=\"50\" fill=\"none\" stroke=\"#bddad5\" stroke-width=\"3\"/><path fill=\"#5dc2af\" d=\"M72 25L42 71 27 56l-4 4 20 20 34-52z\"/></svg>');\n}\n\n.todo-list li label {\n    white-space: pre-line;\n    word-break: break-all;\n    padding: 15px 60px 15px 15px;\n    margin-left: 45px;\n    display: block;\n    line-height: 1.2;\n    transition: color 0.4s;\n}\n\n.todo-list li.completed label {\n    color: #d9d9d9;\n    text-decoration: line-through;\n}\n\n.todo-list li .destroy {\n    display: none;\n    position: absolute;\n    top: 0;\n    right: 10px;\n    bottom: 0;\n    width: 40px;\n    height: 40px;\n    margin: auto 0;\n    font-size: 30px;\n    color: #cc9a9a;\n    margin-bottom: 11px;\n    transition: color 0.2s ease-out;\n}\n\n.todo-list li .destroy:hover {\n    color: #af5b5e;\n}\n\n.todo-list li .destroy:after {\n    content: '×';\n}\n\n.todo-list li:hover .destroy {\n    display: block;\n}\n\n.todo-list li .edit {\n    display: none;\n}\n\n.todo-list li.editing:last-child {\n    margin-bottom: -1px;\n}\n\n.footer {\n    color: #777;\n    padding: 10px 15px;\n    height: 20px;\n    text-align: center;\n    border-top: 1px solid #e6e6e6;\n}\n\n.footer:before {\n    content: '';\n    position: absolute;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    height: 50px;\n    overflow: hidden;\n    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),\n                0 8px 0 -3px #f6f6f6,\n                0 9px 1px -3px rgba(0, 0, 0, 0.2),\n                0 16px 0 -6px #f6f6f6,\n                0 17px 2px -6px rgba(0, 0, 0, 0.2);\n}\n\n.todo-count {\n    float: left;\n    text-align: left;\n}\n\n.todo-count strong {\n    font-weight: 300;\n}\n\n.filters {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n    position: absolute;\n    right: 0;\n    left: 0;\n}\n\n.filters li {\n    display: inline;\n}\n\n.filters li a {\n    color: inherit;\n    margin: 3px;\n    padding: 3px 7px;\n    text-decoration: none;\n    border: 1px solid transparent;\n    border-radius: 3px;\n}\n\n.filters li a.selected,\n.filters li a:hover {\n    border-color: rgba(175, 47, 47, 0.1);\n}\n\n.filters li a.selected {\n    border-color: rgba(175, 47, 47, 0.2);\n}\n\n.clear-completed,\nhtml .clear-completed:active {\n    float: right;\n    position: relative;\n    line-height: 20px;\n    text-decoration: none;\n    cursor: pointer;\n    position: relative;\n}\n\n.clear-completed:hover {\n    text-decoration: underline;\n}\n\n.info {\n    margin: 65px auto 0;\n    color: #bfbfbf;\n    font-size: 10px;\n    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\n    text-align: center;\n}\n\n.info p {\n    line-height: 1;\n}\n\n.info a {\n    color: inherit;\n    text-decoration: none;\n    font-weight: 400;\n}\n\n.info a:hover {\n    text-decoration: underline;\n}\n\n/*\n    Hack to remove background from Mobile Safari.\n    Can't use it globally since it destroys checkboxes in Firefox\n*/\n@media screen and (-webkit-min-device-pixel-ratio:0) {\n    .toggle-all,\n    .todo-list li .toggle {\n        background: none;\n    }\n\n    .todo-list li .toggle {\n        height: 40px;\n    }\n\n    .toggle-all {\n        -webkit-transform: rotate(90deg);\n        transform: rotate(90deg);\n        -webkit-appearance: none;\n        appearance: none;\n    }\n}\n\n@media (max-width: 430px) {\n    .footer {\n        height: 50px;\n    }\n\n    .filters {\n        bottom: 10px;\n    }\n}\n\n"
  },
  {
    "path": "examples/todomvc/webpack.config.js",
    "content": "const path = require(\"path\");\nconst dist = path.resolve(__dirname, \"dist\");\nconst WasmPackPlugin = require(\"@wasm-tool/wasm-pack-plugin\");\n\nmodule.exports = {\n  entry: \"./js/index.js\",\n  output: {\n    publicPath: \"dist/\",\n    path: dist,\n    filename: \"bundle.js\"\n  },\n  plugins: [\n    new WasmPackPlugin({\n      crateDirectory: path.resolve(__dirname, \"crate\"),\n    }),\n  ],\n  devServer: {\n    host: '0.0.0.0',\n  },\n};\n"
  },
  {
    "path": "examples/with_task/.gitignore",
    "content": "dist\nnode_modules\ncrate/pkg\ncrate/target\ncrate/wasm-pack.log\n"
  },
  {
    "path": "examples/with_task/README.md",
    "content": "Just run\n\n```bash\nnpm run start\n```\n\ndev server will come with hot reloading.\n"
  },
  {
    "path": "examples/with_task/crate/.gitignore",
    "content": "\n/target/\n**/*.rs.bk\nCargo.lock\n"
  },
  {
    "path": "examples/with_task/crate/Cargo.toml",
    "content": "[package]\nname = \"with_task\"\nversion = \"0.1.0\"\nauthors = [\"Satoshi Amemiya <amemiya@protonmail.com>\"]\n\n[workspace]\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nsquark = { \"path\" = \"../../../squark\" }\nsquark-macros = { \"path\" = \"../../../squark-macros\" }\nsquark-web = { \"path\" = \"../../../squark-web\" }\nserde_json = \"1.0.13\"\nwasm-bindgen = \"0.2.19\"\nfutures = \"0.1.25\"\njs-sys = \"0.3.6\"\nwasm-bindgen-futures = \"0.3.6\"\n\n[dependencies.web-sys]\nversion = \"0.3.2\"\nfeatures = [\n  'Document',\n  'Window',\n]\n"
  },
  {
    "path": "examples/with_task/crate/src/lib.rs",
    "content": "#![feature(proc_macro_hygiene)]\nextern crate squark;\nextern crate squark_macros;\nextern crate squark_web;\nextern crate wasm_bindgen;\nextern crate wasm_bindgen_futures;\nextern crate web_sys;\nextern crate js_sys;\nextern crate futures;\n\nuse squark::{App, Runtime, View, Task};\nuse squark_macros::view;\nuse squark_web::WebRuntime;\nuse wasm_bindgen::prelude::*;\nuse wasm_bindgen::JsCast;\nuse wasm_bindgen_futures::JsFuture;\nuse web_sys::window;\nuse js_sys::Promise;\nuse futures::prelude::*;\n\n#[derive(Clone, Debug, PartialEq)]\nstruct State {\n    count: isize,\n}\n\nimpl State {\n    pub fn new() -> State {\n        State { count: 0 }\n    }\n}\n\n#[derive(Clone, Debug)]\nenum Action {\n    Increment,\n    Decrement,\n    Timeout,\n}\n\n#[derive(Clone, Debug)]\nstruct CounterApp;\nimpl App for CounterApp {\n    type State = State;\n    type Action = Action;\n\n    fn reducer(&self, mut state: State, action: Action) -> (State, Task<Action>) {\n        let mut task = Task::empty();\n        match action {\n            Action::Increment => {\n                state.count += 1;\n            }\n            Action::Decrement => {\n                state.count -= 1;\n            }\n            Action::Timeout => {\n                let p = Promise::new(&mut move |resolve, _| {\n                    let closure = Closure::wrap(Box::new(move |_: JsValue| {\n                        resolve.call0(&JsValue::null()).unwrap();\n                    }) as Box<FnMut(_)>);\n                    window().unwrap().set_timeout_with_callback_and_timeout_and_arguments_0(closure.as_ref().unchecked_ref(), 1000).unwrap();\n                    closure.forget();\n                });\n                let future = JsFuture::from(p)\n                    .map(move |_| {\n                        Action::Increment\n                    })\n                    .map_err(|e| panic!(\"delay errored; err={:?}\", e));\n                task.push(Box::new(future));\n            }\n        };\n        (state, task)\n    }\n\n    fn view(&self, state: State) -> View<Action> {\n        let count = state.count;\n        view! {\n            <div>\n                { count.to_string() }\n                <button onclick={ move |_| Some(Action::Increment) }>\n                    increment\n                </button>\n                <button onclick={ move |_| Some(Action::Decrement) }>\n                    decrement\n                </button>\n                <button onclick={ move |_| Some(Action::Timeout) }>\n                    timeout\n                </button>\n            </div>\n        }\n    }\n}\n\nimpl Default for CounterApp {\n    fn default() -> CounterApp {\n        CounterApp\n    }\n}\n\n#[wasm_bindgen]\npub fn run() {\n    WebRuntime::<CounterApp>::new(\n        window()\n            .unwrap()\n            .document()\n            .expect(\"Failed to get document\")\n            .query_selector(\"body\")\n            .unwrap()\n            .unwrap(),\n        State::new(),\n    )\n    .run();\n}\n"
  },
  {
    "path": "examples/with_task/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"utf-8\">\n    </head>\n    <body>\n        <script src='./dist/bundle.js'></script>\n    </body>\n</html>\n"
  },
  {
    "path": "examples/with_task/js/index.js",
    "content": "import(\"../crate/pkg\").then(module => {\n  module.run();\n});\n"
  },
  {
    "path": "examples/with_task/package.json",
    "content": "{\n  \"scripts\": {\n    \"start\": \"webpack-dev-server -d\",\n    \"build\": \"webpack --mode none\"\n  },\n  \"devDependencies\": {\n    \"@wasm-tool/wasm-pack-plugin\": \"^0.2.0\",\n    \"webpack\": \"^4.28.1\",\n    \"webpack-cli\": \"^3.2.1\",\n    \"webpack-dev-server\": \"^3.1.14\"\n  }\n}\n"
  },
  {
    "path": "examples/with_task/webpack.config.js",
    "content": "const path = require(\"path\");\nconst dist = path.resolve(__dirname, \"dist\");\nconst WasmPackPlugin = require(\"@wasm-tool/wasm-pack-plugin\");\n\nmodule.exports = {\n  entry: \"./js/index.js\",\n  output: {\n    publicPath: \"dist/\",\n    path: dist,\n    filename: \"bundle.js\"\n  },\n  plugins: [\n    new WasmPackPlugin({\n      crateDirectory: path.resolve(__dirname, \"crate\"),\n    }),\n  ],\n  devServer: {\n    host: '0.0.0.0',\n  },\n};\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"extends\": [\n    \"config:base\"\n  ]\n}\n"
  },
  {
    "path": "squark/.gitignore",
    "content": "\n/target/\n**/*.rs.bk\nCargo.lock\n"
  },
  {
    "path": "squark/Cargo.toml",
    "content": "[package]\nname = \"squark\"\nversion = \"0.7.1\"\nauthors = [\"Satoshi Amemiya <amemiya@protonmail.com>\"]\nrepository = \"https://github.com/rail44/squark\"\nhomepage = \"https://github.com/rail44/squark\"\nlicense = \"WTFPL\"\nreadme = \"README.md\"\nkeywords = [\"web\", \"asmjs\", \"webasm\", \"javascript\"]\ncategories = [\"gui\", \"web-programming\"]\ndescription = \"Virtual DOM implemention and application definition inspired from HyperApp\"\nedition = \"2018\"\n\n[dependencies]\nuuid = \"0.7.4\"\nserde_json = \"1.0.41\"\nrand = { version = \"0.7.2\", features = [ \"wasm-bindgen\" ] }\nrustc-hash = \"1.0.1\"\nfutures = \"0.1.29\"\nserde = \"1.0.101\"\n"
  },
  {
    "path": "squark/src/lib.rs",
    "content": "use rand::prelude::*;\nuse std::cell::{Cell, RefCell};\nuse std::fmt::Debug;\nuse rustc_hash::FxHashMap;\nuse std::rc::Rc;\nuse futures::Future;\nuse serde::Serialize;\n\nmod vdom;\n\npub use crate::vdom::{Node, Element, Diff, View, HandlerArg, AttributeValue, Child};\nuse crate::vdom::{HandlerFunction, HandlerMap};\n\nthread_local! {\n    static RNG: RefCell<SmallRng> = RefCell::new(SmallRng::from_entropy());\n}\n\npub trait App: 'static + Clone + Default {\n    type State: Clone + Debug + PartialEq + 'static;\n    type Action: Clone + Debug + 'static;\n\n    fn reducer(&self, state: Self::State, action: Self::Action) -> (Self::State, Task<Self::Action>);\n\n    fn view(&self, state: Self::State) -> View<Self::Action>;\n}\n\npub fn handler<A, F>(f: F) -> (String, HandlerFunction<A>)\nwhere\n    F: Fn(HandlerArg) -> Option<A> + 'static,\n{\n    (uuid(), Box::new(f))\n}\n\n#[derive(Clone)]\npub struct Env<A: App> {\n    app: A,\n    state: Rc<RefCell<A::State>>,\n    node: Rc<RefCell<Node>>,\n    handler_map: Rc<RefCell<HandlerMap<A::Action>>>,\n    scheduled: Rc<Cell<bool>>,\n}\n\nimpl<A: App> Env<A> {\n    pub fn new(state: A::State) -> Env<A> {\n        Env {\n            app: A::default(),\n            state: Rc::new(RefCell::new(state)),\n            node: Rc::new(RefCell::new(Node::Null)),\n            handler_map: Rc::new(RefCell::new(FxHashMap::default())),\n            scheduled: Rc::new(Cell::new(false)),\n        }\n    }\n\n    fn get_state(&self) -> A::State {\n        self.state.borrow().to_owned()\n    }\n\n    fn set_state(&self, state: A::State) {\n        *self.state.borrow_mut() = state;\n    }\n\n    fn get_node(&self) -> Node {\n        self.node.borrow().to_owned()\n    }\n\n    fn set_node(&self, node: Node) {\n        *self.node.borrow_mut() = node;\n    }\n\n    fn pop_handler(&self, id: &str) -> Option<HandlerFunction<A::Action>> {\n        self.handler_map.borrow_mut().remove(id)\n    }\n}\n\npub struct Task<A>(Vec<Box<Future<Item = A, Error = ()>>>);\n\nimpl<A> Default for Task<A> {\n    fn default() -> Self {\n        Task(vec![])\n    }\n}\n\nimpl<A> Task<A> {\n    pub fn empty() -> Self {\n        Self::default()\n    }\n\n    pub fn into_futures(self) -> Vec<Box<Future<Item = A, Error = ()>>> {\n        self.0\n    }\n\n    pub fn push(&mut self, future: Box<Future<Item = A, Error = ()>>) {\n        self.0.push(future);\n    }\n}\n\npub trait Runtime<A: App>: Clone + 'static {\n    fn get_env<'a>(&'a self) -> &'a Env<A>;\n\n    fn handle_diff(&self, diff: Diff);\n\n    fn handle_future<T: Serialize + 'static, E: Serialize + 'static>(&self, future: Box<Future<Item = T, Error = E>>);\n\n    fn schedule_render(&self);\n\n    fn run(&self) {\n        self.run_with_task(Task::empty());\n    }\n\n    fn run_with_task(&self, task: Task<A::Action>) {\n        for future in task.into_futures() {\n            self.emit_future(future);\n        }\n\n        let env = self.get_env();\n        env.scheduled.set(false);\n        let mut old_node = env.get_node();\n        let view = env.app.view(env.get_state());\n        *env.handler_map.borrow_mut() = view.handler_map;\n        if let Some(diff) = Node::diff(&mut old_node, &view.node, &mut 0) {\n            env.set_node(view.node);\n            self.handle_diff(diff);\n        }\n    }\n\n    fn on_action(&self, action: A::Action) {\n        let env = self.get_env();\n\n        let old_state = env.get_state();\n        let (new_state, task) = env.app.reducer(old_state.to_owned(), action);\n        for future in task.into_futures() {\n            self.emit_future(future);\n        }\n        self.set_state(new_state);\n    }\n\n    fn set_state(&self, new_state: A::State) {\n        let env = self.get_env();\n        let old_state = env.get_state();\n        if old_state == new_state {\n            return;\n        }\n        env.set_state(new_state);\n        if env.scheduled.get() {\n            return;\n        }\n        env.scheduled.set(true);\n        self.schedule_render();\n    }\n\n    fn emit_future(&self, task: Box<Future<Item = A::Action, Error = ()>>) {\n        let this = self.clone();\n        self.handle_future(Box::new(task.map(move |a| {\n            this.on_action(a);\n        })));\n    }\n\n    fn pop_handler(&self, id: &str) -> Option<Box<Fn(HandlerArg)>> {\n        let env = self.get_env();\n        let handler = env.pop_handler(id)?;\n        let this = self.to_owned();\n        let f = move |arg: HandlerArg| {\n            match handler(arg) {\n                Some(a) => this.on_action(a),\n                None => return,\n            };\n        };\n        Some(Box::new(f))\n    }\n}\n\npub fn uuid() -> String {\n    RNG.with(|rng| uuid::Builder::from_bytes(rng.borrow_mut().gen()))\n        .set_variant(uuid::Variant::RFC4122)\n        .set_version(uuid::Version::Random)\n        .build()\n        .to_string()\n}\n"
  },
  {
    "path": "squark/src/vdom.rs",
    "content": "use rustc_hash::{FxHashMap, FxHashSet};\nuse std::iter::FromIterator;\n\npub use serde_json::Value as HandlerArg;\n\ntype Attribute = (String, AttributeValue);\n\nfn diff_attributes(a: &mut Vec<Attribute>, b: &[Attribute]) -> Vec<Diff> {\n    let mut result = vec![];\n\n    let mut old_map = FxHashMap::<String, AttributeValue>::from_iter(a.drain(..));\n    for &(ref new_key, ref new_val) in b {\n        match old_map.remove(new_key) {\n            Some(old_val) => {\n                if &old_val != new_val {\n                    result.push(Diff::SetAttribute(new_key.to_owned(), new_val.to_owned()))\n                }\n            }\n            None => result.push(Diff::SetAttribute(new_key.to_owned(), new_val.to_owned())),\n        }\n    }\n\n    for (old_key, _) in old_map.drain() {\n        result.push(Diff::RemoveAttribute(old_key));\n    }\n\n    result\n}\n\npub(crate) type HandlerFunction<A> = Box<Fn(HandlerArg) -> Option<A>>;\ntype Handler = (String, String);\n\nfn diff_handlers(a: &mut Vec<Handler>, b: &[Handler]) -> Vec<Diff> {\n    let mut result = vec![];\n\n    let mut old_map = FxHashMap::<String, String>::from_iter(a.drain(..));\n    for &(ref new_key, ref new_id) in b {\n        old_map.remove(new_key);\n        result.push(Diff::SetHandler(new_key.to_owned(), new_id.to_owned()));\n    }\n\n    for (old_key, old_id) in old_map.drain() {\n        result.push(Diff::RemoveHandler(old_key, old_id));\n    }\n\n    result\n}\n\n#[derive(Clone, Debug)]\npub enum Node {\n    Text(String),\n    Element(Element),\n    Null,\n}\n\nimpl Node {\n    pub(crate) fn diff(a: &mut Node, b: &Node, i: &mut usize) -> Option<Diff> {\n        match (a, b) {\n            (&mut Node::Element(ref mut a), &Node::Element(ref b)) => {\n                match Element::diff(a, b, *i) {\n                    Some(diff) => Some(diff),\n                    None => None,\n                }\n            }\n            (&mut Node::Text(ref mut text_a), &Node::Text(ref text_b)) => {\n                if text_a == text_b {\n                    return None;\n                }\n                Some(Diff::ReplaceChild(*i, b.to_owned()))\n            }\n            (&mut Node::Null, &Node::Null) => None,\n            (&mut Node::Null, _) => Some(Diff::AddChild(*i, b.to_owned())),\n            (_, &Node::Null) => Some(Diff::RemoveChild(*i)),\n            _ => Some(Diff::ReplaceChild(*i, b.to_owned())),\n        }\n    }\n\n    fn get_key(&self) -> Option<String> {\n        match self {\n            Node::Element(ref el) => el.get_key(),\n            _ => None,\n        }\n    }\n}\n\nfn get_nodelist_key_set(nodelist: &[Node]) -> FxHashSet<String> {\n    FxHashSet::from_iter(nodelist.iter().filter_map(|c| c.get_key()))\n}\n\nfn diff_children(a: &mut Vec<Node>, b: &[Node], i: &mut usize) -> Vec<Diff> {\n    let mut result = vec![];\n    let b_key_set = get_nodelist_key_set(b);\n    let survived = a\n        .drain(..)\n        .filter(|c| match c.get_key() {\n            Some(k) => {\n                let is_survived = b_key_set.contains(&k);\n                if !is_survived {\n                    result.push(Diff::RemoveChild(*i));\n                    return false;\n                }\n                *i += 1;\n                true\n            }\n            None => {\n                *i += 1;\n                true\n            }\n        })\n        .collect();\n    *a = survived;\n\n    let mut i = 0;\n    a.reverse();\n    for new_child in b.iter() {\n        match a.pop() {\n            None => {\n                result.push(Diff::AddChild(i, new_child.to_owned()));\n                i += 1;\n            }\n            Some(mut old_child) => {\n                if let Some(diff) = Node::diff(&mut old_child, new_child, &mut i) {\n                    result.push(diff.to_owned());\n                    if let Diff::RemoveChild(_) = diff {\n                        continue;\n                    }\n                }\n            }\n        }\n        i += 1;\n    }\n\n    for _ in a.iter() {\n        result.push(Diff::RemoveChild(i));\n    }\n\n    result\n}\n\n#[derive(Clone, Debug)]\npub struct Element {\n    name: String,\n    attributes: Vec<Attribute>,\n    handlers: Vec<Handler>,\n    children: Vec<Node>,\n}\n\nimpl Element {\n    fn new(\n        name: String,\n        attributes: Vec<Attribute>,\n        handlers: Vec<Handler>,\n        children: Vec<Node>,\n    ) -> Element {\n        Element {\n            name,\n            attributes,\n            handlers,\n            children,\n        }\n    }\n\n    pub fn name(&self) -> &str {\n        &self.name\n    }\n\n    pub fn attributes(&self) -> &[Attribute] {\n        &self.attributes\n    }\n\n    pub fn handlers(&self) -> &[Handler] {\n        &self.handlers\n    }\n\n    pub fn children(&self) -> &[Node] {\n        &self.children\n    }\n\n    fn diff(a: &mut Element, b: &Element, i: usize) -> Option<Diff> {\n        if let (Some(a_key), Some(b_key)) = (a.get_key(), b.get_key()) {\n            if a_key != b_key {\n                return Some(Diff::ReplaceChild(i, Node::Element(b.to_owned())));\n            }\n        }\n\n        if a.name != b.name {\n            return Some(Diff::ReplaceChild(i, Node::Element(b.to_owned())));\n        }\n\n        let mut result = vec![];\n\n        result.append(&mut diff_attributes(&mut a.attributes, &b.attributes));\n        result.append(&mut diff_handlers(&mut a.handlers, &b.handlers));\n        result.append(&mut diff_children(&mut a.children, &b.children, &mut 0));\n\n        if result.is_empty() {\n            return None;\n        }\n        Some(Diff::PatchChild(i, result))\n    }\n\n    fn get_key(&self) -> Option<String> {\n        self.attributes\n            .iter()\n            .find(|&&(ref k, _)| k == \"key\")\n            .and_then(|&(_, ref v)| match v {\n                AttributeValue::String(ref s) => Some(s.to_owned()),\n                AttributeValue::Bool(_) => None,\n            })\n    }\n}\n\n#[derive(Debug, Clone)]\npub enum Diff {\n    SetAttribute(String, AttributeValue),\n    RemoveAttribute(String),\n    AddChild(usize, Node),\n    ReplaceChild(usize, Node),\n    RemoveChild(usize),\n    PatchChild(usize, Vec<Diff>),\n    SetHandler(String, String),\n    RemoveHandler(String, String),\n}\n\n#[derive(Clone, Debug, PartialEq)]\npub enum AttributeValue {\n    String(String),\n    Bool(bool),\n}\n\nimpl From<String> for AttributeValue {\n    fn from(s: String) -> AttributeValue {\n        AttributeValue::String(s)\n    }\n}\n\nimpl<'a> From<&'a str> for AttributeValue {\n    fn from(s: &'a str) -> AttributeValue {\n        AttributeValue::String(s.to_owned())\n    }\n}\n\nimpl From<bool> for AttributeValue {\n    fn from(b: bool) -> AttributeValue {\n        AttributeValue::Bool(b)\n    }\n}\n\npub(crate) type HandlerMap<A> = FxHashMap<String, HandlerFunction<A>>;\n\npub struct View<A> {\n    pub(crate) node: Node,\n    pub(crate) handler_map: HandlerMap<A>,\n}\n\npub enum Child<A> {\n    View(View<A>),\n    ViewList(Vec<View<A>>),\n}\n\nimpl<A, T> From<T> for Child<A>\nwhere\n    T: Into<View<A>> + Sized,\n{\n    fn from(v: T) -> Child<A> {\n        Child::View(v.into())\n    }\n}\n\nimpl<A> FromIterator<View<A>> for Child<A> {\n    fn from_iter<I>(iter: I) -> Child<A>\n    where\n        I: IntoIterator<Item = View<A>>,\n    {\n        Child::ViewList(iter.into_iter().collect())\n    }\n}\n\nimpl<A> View<A> {\n    pub fn new(\n        name: String,\n        attributes: Vec<Attribute>,\n        handlers: Vec<(String, (String, HandlerFunction<A>))>,\n        children: Vec<Child<A>>,\n    ) -> View<A> {\n        let mut handler_map = FxHashMap::default();\n        let handlers = handlers\n            .into_iter()\n            .map(|(kind, (id, f))| {\n                let handler = (kind, id.to_owned());\n                handler_map.insert(id, f);\n                handler\n            })\n            .collect();\n\n        let mut children_vec = vec![];\n        for child in children {\n            match child {\n                Child::View(v) => {\n                    handler_map.extend(v.handler_map);\n                    children_vec.push(v.node);\n                }\n                Child::ViewList(child_vec) => {\n                    for v in child_vec {\n                        handler_map.extend(v.handler_map);\n                        children_vec.push(v.node);\n                    }\n                }\n            }\n        }\n\n        View {\n            node: Node::Element(Element::new(name, attributes, handlers, children_vec)),\n            handler_map,\n        }\n    }\n\n    pub fn text(s: String) -> View<A> {\n        View {\n            node: Node::Text(s),\n            handler_map: FxHashMap::default(),\n        }\n    }\n\n    pub fn null() -> View<A> {\n        View {\n            node: Node::Null,\n            handler_map: FxHashMap::default(),\n        }\n    }\n}\n\nimpl<A> From<()> for View<A> {\n    fn from(_: ()) -> View<A> {\n        View::null()\n    }\n}\n\nimpl<A> From<String> for View<A> {\n    fn from(s: String) -> View<A> {\n        View::text(s)\n    }\n}\n\nimpl<'a, A> From<&'a str> for View<A> {\n    fn from(s: &'a str) -> View<A> {\n        View::text(s.to_owned())\n    }\n}\n\nimpl<A, T> From<Option<T>> for View<A>\nwhere\n    T: Into<View<A>>,\n{\n    fn from(option: Option<T>) -> View<A> {\n        option.map_or_else(View::null, |v| v.into())\n    }\n}\n"
  },
  {
    "path": "squark-macros/.gitignore",
    "content": "\n/target/\n**/*.rs.bk\nCargo.lock\n"
  },
  {
    "path": "squark-macros/Cargo.toml",
    "content": "[package]\nname = \"squark-macros\"\nversion = \"0.7.0\"\nauthors = [\"Satoshi Amemiya <amemiya@protonmail.com>\"]\nrepository = \"https://github.com/rail44/squark\"\nhomepage = \"https://github.com/rail44/squark\"\nlicense = \"WTFPL\"\nreadme = \"README.md\"\nkeywords = [\"web\", \"asmjs\", \"webasm\", \"javascript\"]\ncategories = [\"gui\", \"web-programming\"]\ndescription = \"Macros like JSX to help building Squark application\"\n\n[lib]\nproc-macro = true\n\n[dependencies]\npest = \"2.1.2\"\npest_derive = \"2.1.0\"\n\n[dev-dependencies]\nsquark = { path = \"../squark\", version = \"0.7.0\" }\n"
  },
  {
    "path": "squark-macros/src/lib.rs",
    "content": "#![crate_type = \"proc-macro\"]\n#![feature(proc_macro_hygiene, proc_macro_quote)]\n\nextern crate pest;\n#[macro_use]\nextern crate pest_derive;\nextern crate proc_macro;\n\nuse parser::{Parser as ViewParser, Rule};\nuse pest::iterators::{Pair, Pairs};\nuse pest::Parser;\nuse proc_macro::{quote, Literal, TokenStream, TokenTree};\nuse std::iter::FromIterator;\nuse std::str::FromStr;\n\nmod parser {\n    #[derive(Parser)]\n    #[grammar = \"view.pest\"]\n    pub struct Parser;\n}\n\nfn get_token_stream(mut tag_pairs: Pairs<Rule>) -> TokenStream {\n    let name = tag_pairs.next().expect(\"name\").as_str();\n    let _name = TokenTree::Literal(Literal::string(name));\n\n    let mut attributes = vec![];\n    let mut handlers = vec![];\n\n    let vec: Vec<Pair<Rule>> = tag_pairs.next().expect(\"attributes\").into_inner().collect();\n    for i in 0..(vec.len() / 2) {\n        let j = i * 2;\n        let k = &vec[j].as_str();\n        let v = &vec[j + 1];\n\n        let _v = match v.as_rule() {\n            Rule::embedded => {\n                let mut _embedded = TokenStream::from_str(v.as_str()).unwrap();\n                quote!($_embedded.into())\n            }\n            Rule::string => {\n                let _v = TokenTree::Literal(Literal::string(v.as_str()));\n                quote! { $_v.into() }\n            }\n            Rule::bool => {\n                let _v = TokenStream::from_str(v.as_str()).unwrap();\n                quote! { $_v.into() }\n            }\n            _ => unreachable!(),\n        };\n\n        if k.starts_with(\"on\") {\n            let (_, k) = k.split_at(2);\n            let _k = TokenTree::Literal(Literal::string(k));\n            handlers.push(quote! {\n                ($_k.to_string(), _squark::handler($_v)),\n            });\n            continue;\n        }\n\n        let _k = TokenTree::Literal(Literal::string(k));\n        attributes.push(quote! {\n            ($_k.to_string(), $_v),\n        });\n    }\n    let _attributes = TokenStream::from_iter(attributes);\n    let _handlers = TokenStream::from_iter(handlers);\n\n    let mut children = vec![];\n    if let Some(children_pair) = tag_pairs.next() {\n        for p in children_pair.into_inner() {\n            let token = match p.as_rule() {\n                Rule::tag => {\n                    let _tag = get_token_stream(p.into_inner());\n                    quote! {\n                        _squark::Child::from($_tag),\n                    }\n                }\n                Rule::text => {\n                    let _text = TokenTree::Literal(Literal::string(p.as_str()));\n                    quote! {\n                        $_text.into(),\n                    }\n                }\n                Rule::embedded => {\n                    let _embedded = TokenStream::from_str(p.as_str()).unwrap();\n                    quote! {\n                        {$_embedded}.into(),\n                    }\n                }\n                _ => unreachable!(),\n            };\n            children.push(token);\n        }\n    }\n    let _children = TokenStream::from_iter(children);\n\n    quote! {\n        _squark::View::new(\n            $_name.to_string(),\n            vec![\n                $_attributes\n            ],\n            vec![\n                $_handlers\n            ],\n            vec![\n                $_children\n            ]\n        )\n    }\n}\n\n#[proc_macro]\npub fn view(arg: TokenStream) -> TokenStream {\n    let s = arg.to_string();\n    let mut pairs = ViewParser::parse(Rule::view, &s).unwrap();\n    let _token = get_token_stream(pairs.next().unwrap().into_inner());\n\n    quote! {\n        {\n            extern crate squark as _squark;\n            $_token\n        }\n    }\n}\n"
  },
  {
    "path": "squark-macros/src/view.pest",
    "content": "view = _{ SOI ~ tag ~ EOI }\n\nidentifier = _{ ('a'..'z' | 'A'..'Z' | '0'..'9' | \"_\" | \"-\")+ }\n\ntag_name = @{ identifier }\ntag = { single_tag | pair_tag }\n\npair_tag = _{ open_tag ~ children ~ close_tag }\nsingle_tag = _{ \"<\" ~ tag_name ~ attributes ~ \"/\" ~ \">\" }\n\nchildren = {(tag | embedded_outer | text)*}\n\nopen_tag = _{ \"<\" ~ tag_name ~ attributes ~ \">\" }\nclose_tag = _{ \"<\" ~ \"/\" ~ (!\">\" ~ ANY)+ ~ \">\" }\n\nattributes = { attribute* }\nattribute = _{ key ~ \"=\" ~ (string_literal | bool | embedded_outer) }\n\nkey = @{ identifier }\n\nstring_literal = _{ \"\\\"\" ~ string ~ \"\\\"\" }\nstring = @{ (!\"\\\"\" ~ ANY)* }\n\nembedded_outer = _{ \"{\" ~ embedded ~ \"}\" }\nembedded = { (bracketed | (!\"}\" ~ ANY))* }\nbracketed = _{ \"{\" ~ bracketed_inner ~ \"}\" }\nbracketed_inner = _{ (bracketed | (!\"}\" ~ ANY))* }\n\nbool = { \"true\" | \"false\" }\n\ntext = @{ (!(\"<\" | \"{\") ~ ANY)+ }\n\nnewline    = _{ \"\\n\" | \"\\r\\n\" }\nWHITESPACE = _{ \" \" | newline }\n"
  },
  {
    "path": "squark-macros/tests/test.rs",
    "content": "#![feature(test, use_extern_macros, proc_macro_non_items)]\n\nextern crate squark;\nextern crate squark_macros;\n\nuse squark::View;\nuse squark_macros::view;\n\nfn v() -> View<()> {\n    let not_completed_count = 1234;\n    let has_completed = true;\n    view! {\n        <footer class=\"footer\">\n            <h1 class=\"todo-count\">\n                <strong>{ not_completed_count.to_string() }</strong>\n                 item(s) left\n            </h1>\n            <br />\n            {\n                if has_completed {\n                    view! {\n                        <button class=\"clear-completed\" onclick={ move |_| { Some(()) } } />\n                    }\n                } else {\n                    ().into()\n                }\n            }\n        </footer>\n    }\n}\n\n#[test]\nfn it_works() {\n    let v = v();\n}\n"
  },
  {
    "path": "squark-web/.gitignore",
    "content": "\n/target/\n**/*.rs.bk\nCargo.lock\n"
  },
  {
    "path": "squark-web/Cargo.toml",
    "content": "[package]\nname = \"squark-web\"\nversion = \"0.3.0\"\nauthors = [\"Satoshi Amemiya <amemiya@protonmail.com>\"]\nrepository = \"https://github.com/rail44/squark\"\nhomepage = \"https://github.com/rail44/squark\"\nlicense = \"WTFPL\"\nreadme = \"README.md\"\ncategories = [\"gui\", \"web-programming\", \"wasm\"]\ndescription = \"Squark runtime implemiontion for web browser with using wasm-bindgen\"\nedition = \"2018\"\n\n[dependencies]\nserde_json = \"1.0.41\"\nserde = \"1.0.101\"\nsquark = { path = \"../squark\", version = \"0.7.0\" }\nwasm-bindgen = { version = \"0.2.51\", features = [ \"nightly\", \"serde-serialize\" ] }\njs-sys = \"0.3.28\"\nfutures = \"0.1.29\"\nwasm-bindgen-futures = \"0.4.1\"\n\n[dependencies.web-sys]\nversion = \"0.3.28\"\nfeatures = [\n  'Window',\n  'Document',\n  'DomStringMap',\n  'Element',\n  'EventTarget',\n  'HtmlElement',\n  'HtmlInputElement',\n  'Node',\n  'NodeList',\n  'Text',\n  'Event',\n  'InputEvent',\n  'KeyboardEvent',\n]\n"
  },
  {
    "path": "squark-web/src/lib.rs",
    "content": "use std::cell::RefCell;\nuse std::collections::HashMap;\nuse std::rc::Rc;\nuse futures::Future;\nuse wasm_bindgen_futures::future_to_promise;\nuse squark::{\n    uuid,\n    App, AttributeValue, Diff, Element as SquarkElement, Env, HandlerArg, Node as SquarkNode,\n    Runtime,\n};\nuse wasm_bindgen::prelude::*;\nuse wasm_bindgen::JsCast;\nuse web_sys::{window, Document, Element, EventTarget, HtmlElement, Node};\nuse serde::Serialize;\nuse serde_json::json;\n\ntrait ToHandlerArg: JsCast {\n    fn to_handler_arg(self) -> HandlerArg;\n}\n\nimpl ToHandlerArg for web_sys::Event {\n    fn to_handler_arg(self) -> HandlerArg {\n        json!{null}\n    }\n}\n\nimpl ToHandlerArg for web_sys::InputEvent {\n    fn to_handler_arg(self) -> HandlerArg {\n        let ev: web_sys::Event = self.into();\n        let target = ev.target().unwrap();\n        let js_val: &JsValue = target.as_ref();\n        if js_val.is_null() {\n            return json!{\"\"};\n        }\n        let input_el: &web_sys::HtmlInputElement = target.unchecked_ref();\n        json!{input_el.value()}\n    }\n}\n\nimpl ToHandlerArg for web_sys::KeyboardEvent {\n    fn to_handler_arg(self) -> HandlerArg {\n        json!{self.key()}\n    }\n}\n\ntype AttachedMap = HashMap<String, HashMap<String, Closure<Fn(JsValue)>>>;\n\nfn document() -> Document {\n    window().unwrap().document().unwrap()\n}\n\nfn get_handler_id(el: &HtmlElement) -> Option<String> {\n    let id = js_sys::Reflect::get(el.dataset().as_ref(), &\"handlerId\".into()).unwrap();\n\n    if id.is_undefined() {\n        return None;\n    }\n    Some(id.as_string().unwrap())\n}\n\nfn set_handler_id(el: &HtmlElement, id: &str) {\n    js_sys::Reflect::set(el.dataset().as_ref(), &\"handlerId\".into(), &id.into()).unwrap();\n}\n\n#[derive(Clone)]\npub struct WebRuntime<A: App> {\n    env: Env<A>,\n    root: Rc<Element>,\n    attached_map: Rc<RefCell<AttachedMap>>,\n}\n\nfn insert_at(parent: &Node, i: usize, node: &Node) {\n    let ref_node = parent.child_nodes().item(i as u32);\n    if ref_node.is_none() {\n        parent.append_child(&node).unwrap();\n        return;\n    }\n    parent.insert_before(&node, ref_node.as_ref()).unwrap();\n}\n\nfn set_attribute(el: &Element, name: &str, value: &AttributeValue) {\n    match value {\n        AttributeValue::Bool(b) => {\n            js_sys::Reflect::set(el.as_ref(), &name.into(), &(*b).into()).unwrap();\n            el.set_attribute(name, &b.to_string()).unwrap();\n        }\n        AttributeValue::String(s) => {\n            js_sys::Reflect::set(el.as_ref(), &name.into(), &s.into()).unwrap();\n            el.set_attribute(name, s).unwrap();\n        }\n    }\n}\n\nimpl<A: App> WebRuntime<A> {\n    pub fn new(root: Element, state: A::State) -> WebRuntime<A> {\n        WebRuntime {\n            env: Env::new(state),\n            root: Rc::new(root),\n            attached_map: Rc::new(RefCell::new(AttachedMap::new())),\n        }\n    }\n\n    fn handle_diff_inner(&self, el: &Element, diff: Diff) {\n        match diff {\n            Diff::AddChild(i, node) => self.add_child(el, i, node),\n            Diff::PatchChild(i, diffs) => {\n                let as_node: &Node = el.as_ref();\n                let child = as_node.child_nodes().item(i as u32).unwrap();\n                for diff in diffs {\n                    self.handle_diff_inner(child.unchecked_ref(), diff);\n                }\n            }\n            Diff::ReplaceChild(i, node) => self.replace_child(el, i, node),\n            Diff::SetAttribute(name, value) => set_attribute(el, &name, &value),\n            Diff::RemoveAttribute(name) => {\n                el.remove_attribute(&name).unwrap();\n            }\n            Diff::RemoveChild(i) => self.remove_child(el.as_ref(), i),\n            Diff::SetHandler(name, id) => self.set_handler(el.unchecked_ref(), &name, &id),\n            Diff::RemoveHandler(name, _) => {\n                let attached = self\n                    .attached_map\n                    .borrow_mut()\n                    .get_mut(&get_handler_id(el.unchecked_ref()).unwrap())\n                    .and_then(|inner| inner.remove(&name))\n                    .unwrap();\n                let html_el: &EventTarget = el.unchecked_ref();\n                html_el\n                    .remove_event_listener_with_callback(&name, attached.as_ref().unchecked_ref())\n                    .unwrap();\n            }\n        }\n    }\n\n    fn replace_at(&self, parent: &Node, i: usize, node: &Node) {\n        let current = parent.child_nodes().item(i as u32).unwrap();\n        self.remove_attached(&current);\n        parent.replace_child(&node, &current).unwrap();\n    }\n\n    fn create_element(&self, el: &SquarkElement) -> Element {\n        let web_el: Element = document().create_element(el.name()).unwrap();\n        for (ref name, ref value) in el.attributes() {\n            set_attribute(&web_el, name, value);\n        }\n\n        for (ref name, id) in el.handlers() {\n            self.set_handler(web_el.unchecked_ref(), name, &id);\n        }\n\n        {\n            let node: &Node = web_el.as_ref();\n            for child in el.children() {\n                match child {\n                    SquarkNode::Element(el) => {\n                        let child = self.create_element(el);\n                        node.append_child(child.as_ref()).unwrap();\n                    }\n                    SquarkNode::Text(s) => {\n                        let child = document().create_text_node(s.as_str());\n                        node.append_child(child.as_ref()).unwrap();\n                    }\n                    _ => (),\n                };\n            }\n        }\n\n        web_el\n    }\n\n    fn add_child(&self, parent: &Element, i: usize, node: SquarkNode) {\n        match node {\n            SquarkNode::Element(el) => {\n                let child = self.create_element(&el);\n                insert_at(parent.as_ref(), i, child.as_ref());\n            }\n            SquarkNode::Text(s) => {\n                let child = document().create_text_node(s.as_str());\n                insert_at(parent.as_ref(), i, child.as_ref());\n            }\n            _ => (),\n        };\n    }\n\n    fn replace_child(&self, parent: &Element, i: usize, node: SquarkNode) {\n        match node {\n            SquarkNode::Element(el) => {\n                let child = self.create_element(&el);\n                self.replace_at(parent.as_ref(), i, child.as_ref());\n            }\n            SquarkNode::Text(s) => {\n                let child = document().create_text_node(s.as_str());\n                self.replace_at(parent.as_ref(), i, child.as_ref());\n            }\n            _ => (),\n        };\n    }\n\n    fn remove_child(&self, parent: &Node, i: usize) {\n        let current = parent.child_nodes().item(i as u32).unwrap();\n\n        self.remove_attached(current.unchecked_ref());\n        parent.remove_child(&current).unwrap();\n    }\n\n    fn set_handler(&self, el: &Element, name: &str, id: &str) {\n        let closure = match name {\n            \"keydown\" => self._set_handler::<web_sys::KeyboardEvent>(el.as_ref(), \"keydown\", id),\n            \"input\" => self._set_handler::<web_sys::InputEvent>(el.as_ref(), \"input\", id),\n            name => self._set_handler::<web_sys::Event>(el.as_ref(), name, id),\n        };\n\n        let handler_id = get_handler_id(el.unchecked_ref()).unwrap_or_else(|| {\n            let uuid = uuid();\n            set_handler_id(el.unchecked_ref(), &uuid);\n            uuid\n        });\n\n        let mut map = self.attached_map.borrow_mut();\n        let inner = map.entry(handler_id).or_insert_with(HashMap::new);\n        if let Some(attached) = inner.remove(name) {\n            let target: &EventTarget = el.as_ref();\n            target\n                .remove_event_listener_with_callback(&name, attached.as_ref().unchecked_ref())\n                .unwrap();\n        }\n        inner.insert(name.to_owned(), closure);\n    }\n\n    fn _set_handler<T: ToHandlerArg>(\n        &self,\n        el: &EventTarget,\n        name: &str,\n        id: &str,\n    ) -> Closure<Fn(JsValue)> {\n        let handler = self.pop_handler(id).unwrap();\n        let closure = Closure::new(move |ev: JsValue| {\n            let ev: T = ev.unchecked_into();\n            handler(ev.to_handler_arg());\n        });\n        el.add_event_listener_with_callback(name, closure.as_ref().unchecked_ref())\n            .unwrap();\n        closure\n    }\n\n    fn remove_attached(&self, el: &Node) {\n        if !el.is_instance_of::<Element>() {\n            return;\n        }\n\n        let el: &Element = el.unchecked_ref();\n\n        let mut map = self.attached_map.borrow_mut();\n        if let Some(id) = get_handler_id(el.unchecked_ref()) {\n            map.remove(&id);\n        }\n\n        let children = el.query_selector_all(\"[data-has-handler]\").unwrap();\n        for i in 0..children.length() {\n            let child = children.item(i).unwrap();\n            if let Some(id) = get_handler_id(child.unchecked_ref()) {\n                map.remove(&id);\n            }\n        }\n    }\n}\n\nfn nop<T>(_: T) {}\n\nimpl<A: App> Runtime<A> for WebRuntime<A> {\n    fn get_env<'a>(&'a self) -> &'a Env<A> {\n        &self.env\n    }\n\n    fn schedule_render(&self) {\n        let this = self.clone();\n        let closure = Closure::wrap(Box::new(move |_: JsValue| {\n            this.run();\n        }) as Box<FnMut(_)>);\n        window()\n            .unwrap()\n            .request_animation_frame(closure.as_ref().unchecked_ref())\n            .unwrap();\n        closure.forget();\n    }\n\n    fn handle_diff(&self, diff: Diff) {\n        self.handle_diff_inner(&self.root, diff);\n    }\n\n    fn handle_future<T: Serialize + 'static, E: Serialize + 'static>(&self, future: Box<Future<Item = T, Error = E>>) {\n        let p = future_to_promise(\n            future\n                .map(|v| JsValue::from_serde(&v).unwrap())\n                .map_err(|e| JsValue::from_serde(&e).unwrap())\n        );\n        let closure = Closure::new(nop);\n        p.then(&closure);\n        closure.forget();\n    }\n}\n"
  }
]