perspective-viewer 5.6.1

The `<perspective-viewer>` Custom Element: a WebAssembly data grid, pivot table and charting UI for large, real-time and streaming datasets.
Documentation
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

use ::futures::channel::oneshot::*;
use perspective_js::utils::{global, *};
use wasm_bindgen::prelude::*;

/// An `async` version of `queueMicrotask()`, curiously absent from [`web_sys`].
pub async fn await_queue_microtask() -> ApiResult<()> {
    #[wasm_bindgen]
    extern "C" {
        #[wasm_bindgen(js_name = Window)]
        type QMWindow;

        #[wasm_bindgen(catch, method, structural, js_class = "Window", js_name = queueMicrotask)]
        fn queue_microtask(this: &QMWindow, callback: &::js_sys::Function) -> Result<i32, JsValue>;
    }

    let (sender, receiver) = channel::<()>();
    let jsfun = Closure::once_into_js(move || sender.send(()).unwrap());
    js_sys::global()
        .dyn_into::<QMWindow>()
        .unwrap()
        .queue_microtask(jsfun.unchecked_ref())?;

    Ok(receiver.await?)
}

/// An `async` version of `requestAnimationFrame()`, which resolves on the next
/// animation frame.
pub async fn request_animation_frame() {
    let (sender, receiver) = channel::<()>();
    let jsfun = Closure::once_into_js(move || sender.send(()).unwrap());
    global::window()
        .request_animation_frame(jsfun.unchecked_ref())
        .unwrap();

    receiver.await.unwrap()
}

/// An `async` which awaits the browser's `load` event, which is automatically
/// bypassed if `document.readyState` indicates this has already occurred.
/// Resolve once the DOM is interactive and every linked stylesheet has
/// APPLIED (`link.sheet` set) — the point at which computed styles are
/// readable. Never await the `load` EVENT here: an in-flight `@font-face`
/// request delays `load` on Firefox, and the font loader that depends on
/// this function is itself what manages those requests.
pub async fn await_dom_loaded() -> ApiResult<()> {
    loop {
        let document = global::document();
        let state = document.ready_state();
        if state == "complete" || state == "loaded" {
            return Ok(());
        }

        if state != "loading" {
            let links = document.query_selector_all("link[rel=stylesheet]")?;
            let applied = (0..links.length()).all(|i| {
                links
                    .item(i)
                    .and_then(|node| node.dyn_into::<web_sys::HtmlLinkElement>().ok())
                    .is_none_or(|link| link.sheet().is_some())
            });

            if applied {
                return Ok(());
            }
        }

        set_timeout(50).await?;
    }
}

/// An `async` version of `set_timeout`, which resolves in `timeout`
/// milliseconds
pub async fn set_timeout(timeout: i32) -> ApiResult<()> {
    let (sender, receiver) = channel::<()>();
    let jsfun = Closure::once_into_js(move || {
        let _ = sender.send(());
    });

    global::window()
        .set_timeout_with_callback_and_timeout_and_arguments_0(jsfun.unchecked_ref(), timeout)?;

    Ok(receiver.await?)
}