Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions docs/md/how_to/javascript/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ these themes directly into your app, and the `perspective-viewer`s will be
themed accordingly:

```javascript
// Themes based on Thought Merchants's Prospective design
// Themes based on Thought Merchants's Prospective design. `pro.css` ("Pro")
// follows the page's `color-scheme`, or `prefers-color-scheme` when the page
// declares none; `pro-light.css` ("Pro Light") and `pro-dark.css`
// ("Pro Dark") pin one scheme.
import "@perspective-dev/viewer/dist/css/pro.css";
import "@perspective-dev/viewer/dist/css/pro-light.css";
import "@perspective-dev/viewer/dist/css/pro-dark.css";

// Other themes
Expand Down Expand Up @@ -73,7 +77,10 @@ await viewer.restore({ theme: "Pro Dark" });
The best way to write a new theme is to
[fork and modify an existing theme](https://github.com/perspective-dev/perspective/tree/master/rust/perspective-viewer/src/themes),
which are _just_ collections of regular CSS variables — Perspective's own
themes are plain `.css` files, with no preprocessor involved.
themes are plain `.css` files, with no preprocessor involved (the one
exception is the `Pro` family, whose three themes share one `light-dark()`
palette).

`<perspective-viewer>` is
not "themed" by default and will lack icons and label text in addition to colors
and fonts, so starting from an empty theme forces you to define _every_
Expand Down
2 changes: 1 addition & 1 deletion examples/esbuild-example/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import perspective_viewer from "@perspective-dev/viewer";
import "@perspective-dev/viewer-datagrid";
import "@perspective-dev/viewer-charts";

import "@perspective-dev/viewer/dist/css/pro-dark.css";
import "@perspective-dev/viewer/dist/css/themes.css";
import "./index.css";

import arrow from "superstore-arrow/superstore.lz4.arrow";
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"build": "node tools/scripts/build.mjs",
"build_python": "node tools/scripts/build.mjs",
"bench": "node tools/scripts/bench.mjs",
"start": "npm run start --workspace",
"setup": "node tools/scripts/setup.mjs",
"once": "PSP_ONCE=1 node tools/scripts/setup.mjs",
"docs": "node tools/scripts/docs.mjs",
Expand Down
12 changes: 4 additions & 8 deletions packages/jupyterlab/test/jupyter/widget.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ async_table = await async_client.open_table(sync_table.get_name())`,
sort: [],
split_by: [],
table: expect.any(String),
theme: "Pro Light",
theme: "Pro",
title: null,
});

Expand Down Expand Up @@ -483,7 +483,7 @@ w.theme = "Pro Dark"`,
sort: [],
split_by: [],
table: expect.any(String),
theme: "Pro Light",
theme: "Pro",
title: null,
});

Expand Down Expand Up @@ -716,9 +716,7 @@ assert w2.group_by == ["bool"]
async ({ page }) => {
await default_body(page);
await expect
.poll(() =>
page.locator("regular-table tbody tr").count(),
)
.poll(() => page.locator("regular-table tbody tr").count())
.toEqual(10);
},
);
Expand Down Expand Up @@ -824,9 +822,7 @@ assert w2.group_by == ["bool"]
const viewer = await default_body(page);
await expect
.poll(() =>
viewer.evaluate(
async (v) => (await v.save()).group_by,
),
viewer.evaluate(async (v) => (await v.save()).group_by),
)
.toEqual(["date"]);
const columns = await viewer.evaluate(
Expand Down
15 changes: 13 additions & 2 deletions packages/viewer-charts/src/ts/theme/theme-snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,35 @@ const THEME_VARS = [
"--psp-charts--sunburst-gap--px",
];

/**
* The computed value of an unset `--psp-*` color, which the theme defaults
* register with `@property` so `light-dark()` resolves before it is read.
*/
const UNSET_COLOR = "rgba(0, 0, 0, 0)";

/**
* Capture every CSS variable the renderer cares about into a
* structured-cloneable map. Series-palette colours are walked until the
* first missing var.
*/
export function snapshotThemeVars(el: Element): ThemeSnapshot {
const style = getComputedStyle(el);
const read = (name: string) => {
const raw = style.getPropertyValue(name).trim();
return raw === UNSET_COLOR ? "" : raw;
};

const out: ThemeSnapshot = {};
for (const v of THEME_VARS) {
const raw = style.getPropertyValue(v).trim();
const raw = read(v);
if (raw) {
out[v] = raw;
}
}

for (let i = 1; ; i++) {
const key = `--psp-charts--series-${i}--color`;
const raw = style.getPropertyValue(key).trim();
const raw = read(key);
if (!raw) {
break;
}
Expand Down
4 changes: 3 additions & 1 deletion packages/viewer-datagrid/src/ts/model/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,11 @@ function nestedArraysChanged<T>(a: T[][], b: T[][]): boolean {
return false;
}

const UNSET_COLOR = "rgba(0, 0, 0, 0)";

function get_rule(regular: HTMLElement, tag: string, def: string): string {
const color = window.getComputedStyle(regular).getPropertyValue(tag).trim();
if (color.length > 0) {
if (color.length > 0 && color !== UNSET_COLOR) {
return color;
} else {
return def;
Expand Down
19 changes: 10 additions & 9 deletions packages/viewer-datagrid/src/ts/plugin/plugin_config_schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,25 +83,26 @@ export function measure_px(
return Number.isFinite(px) && px > 0 ? Math.round(px) : fallback;
}

const UNSET_COLOR = "rgba(0, 0, 0, 0)";

/**
* The theme's zebra stripe color as opaque `#rrggbb`, a 6% blend of text
* into background when the theme defines none.
*/
export function default_zebra_color(elem: Element): string {
const style = window.getComputedStyle(elem);
const own = style.getPropertyValue("--psp-datagrid--zebra--color").trim();
const read = (name: string) => {
const raw = style.getPropertyValue(name).trim();
return raw === UNSET_COLOR ? "" : raw;
};

const own = read("--psp-datagrid--zebra--color");
if (own) {
return rgbToHex(parseColor(own));
}

const background = parseColor(
style.getPropertyValue("--psp--background-color").trim() || "#ffffff",
);

const color = parseColor(
style.getPropertyValue("--psp--color").trim() || "#000000",
);

const background = parseColor(read("--psp--background-color") || "#ffffff");
const color = parseColor(read("--psp--color") || "#000000");
return rgbToHex(mixRgb(background, color, 0.06));
}

Expand Down
5 changes: 4 additions & 1 deletion packages/viewer-datagrid/test/js/restyle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async function goto_ready(page: any) {
await new Promise((x) => setTimeout(x, 10));
}

// `basic-test.html` loads only `pro.css` (= "Pro Light"); "Pro Dark"
// `basic-test.html` loads only `pro-light.css` (= "Pro Light"); "Pro Dark"
// is a separate stylesheet, without which a theme switch changes no
// CSS at all.
await new Promise((resolve, reject) => {
Expand All @@ -39,6 +39,9 @@ async function goto_ready(page: any) {
link.onerror = reject;
document.head.appendChild(link);
});

const viewer = document.querySelector("perspective-viewer") as any;
await viewer.resetThemes();
});
}

Expand Down
3 changes: 3 additions & 0 deletions rust/perspective-js/src/rust/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ extern "C" {
#[wasm_bindgen(typescript_type = "ViewConfigUpdate")]
pub type JsViewConfig;

#[wasm_bindgen(typescript_type = "ViewConfig")]
pub type JsResolvedViewConfig;

#[wasm_bindgen(typescript_type = "UpdateOptions")]
pub type JsUpdateOptions;

Expand Down
7 changes: 5 additions & 2 deletions rust/perspective-js/src/rust/utils/futures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use wasm_bindgen::describe::WasmDescribe;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::{JsFuture, future_to_promise};

use super::TsPromise;
use super::errors::*;

#[wasm_bindgen]
Expand Down Expand Up @@ -171,15 +172,17 @@ where
impl<T> WasmDescribe for ApiFuture<T>
where
Result<T, JsValue>: IntoJsResult + 'static,
T: TsPromise,
{
fn describe() {
<js_sys::Promise as WasmDescribe>::describe()
<T::Promise as WasmDescribe>::describe()
}
}

impl<T> IntoWasmAbi for ApiFuture<T>
where
Result<T, JsValue>: IntoJsResult + 'static,
T: TsPromise,
{
type Abi = <js_sys::Promise as IntoWasmAbi>::Abi;

Expand All @@ -192,7 +195,7 @@ where
impl<T> FromWasmAbi for ApiFuture<T>
where
Result<T, JsValue>: IntoJsResult + 'static,
T: From<JsValue> + Into<JsValue>,
T: From<JsValue> + Into<JsValue> + TsPromise,
{
type Abi = <js_sys::Promise as IntoWasmAbi>::Abi;

Expand Down
2 changes: 2 additions & 0 deletions rust/perspective-js/src/rust/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod local_poll_loop;
mod serde;
#[cfg(feature = "trace-allocator")]
mod trace_allocator;
mod ts_promise;

pub use browser::*;
pub use console_logger::*;
Expand All @@ -30,3 +31,4 @@ pub use local_poll_loop::*;
pub use serde::*;
#[cfg(feature = "trace-allocator")]
pub(crate) use trace_allocator::get_used;
pub use ts_promise::*;
50 changes: 50 additions & 0 deletions rust/perspective-js/src/rust/utils/ts_promise.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ 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 wasm_bindgen::describe::WasmDescribe;

/// The `Promise<T>` TypeScript type an [`super::ApiFuture<T>`] is declared
/// as, carried by a `#[wasm_bindgen]` extern type whose `typescript_type`
/// names it (the wasm-bindgen descriptor interpreter runs no loops, so the
/// name must be a compile-time literal).
pub trait TsPromise {
type Promise: WasmDescribe;
}

/// Declare the `Promise<T>` TypeScript type of each listed `ApiFuture<T>`
/// payload, e.g. `ts_promise! { View => JsViewPromise = "Promise<View>"; }`.
#[macro_export]
macro_rules! ts_promise {
($($t:ty => $alias:ident = $ts:literal;)*) => {$(
#[::wasm_bindgen::prelude::wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = $ts)]
pub type $alias;
}

impl $crate::utils::TsPromise for $t {
type Promise = $alias;
}
)*};
}

ts_promise! {
() => JsVoidPromise = "Promise<void>";
u32 => JsNumberPromise = "Promise<number>";
String => JsStringPromise = "Promise<string>";
Vec<u8> => JsUint8ArrayPromise = "Promise<Uint8Array>";
Vec<String> => JsStringArrayPromise = "Promise<string[]>";
crate::Client => JsClientPromise = "Promise<Client>";
crate::Table => JsTablePromise = "Promise<Table>";
crate::View => JsViewPromise = "Promise<View>";
crate::JsResolvedViewConfig => JsResolvedViewConfigPromise = "Promise<ViewConfig>";
}
2 changes: 2 additions & 0 deletions rust/perspective-viewer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ web-sys.features = [
"CssRuleList",
"CssRule",
"CssStyleRule",
"CssGroupingRule",
"CssImportRule",
"CustomEvent",
"CustomEventInit",
"DataTransfer",
Expand Down
13 changes: 12 additions & 1 deletion rust/perspective-viewer/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import {
buildDocsCorpus,
get_host,
inlineUrlVisitor,
mixinVisitor,
resolveNPM,
THEME_AT_RULES,
} from "./tools.mjs";

const IS_DEBUG =
Expand Down Expand Up @@ -162,6 +164,7 @@ export async function build_all() {
"icons",
"intl",
"pro",
"pro-light",
"pro-dark",
"botanical",
"monokai",
Expand All @@ -180,12 +183,20 @@ export async function build_all() {
"themes",
];

const palettes = fs
.readdirSync("./src/themes/pro")
.map((name) => `./src/themes/pro/${name}`);

for (const name of themes) {
const filename = `./src/themes/${name}.css`;
const { code } = await bundleAsyncCss({
filename,
minify: true,
visitor: inlineUrlVisitor(filename),
customAtRules: THEME_AT_RULES,
visitor: composeVisitors([
inlineUrlVisitor(filename),
mixinVisitor(palettes),
]),
resolver: resolveNPM(import.meta.url),
});

Expand Down
6 changes: 5 additions & 1 deletion rust/perspective-viewer/docs/viewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ these themes directly into your app, and the `perspective-viewer`s will be
themed accordingly:

```javascript
// Themes based on Thought Merchants's Prospective design
// Themes based on Thought Merchants's Prospective design. `pro.css` ("Pro")
// follows the page's `color-scheme`, or `prefers-color-scheme` when the page
// declares none; `pro-light.css` ("Pro Light") and `pro-dark.css`
// ("Pro Dark") pin one scheme.
import "@perspective-dev/viewer/dist/css/pro.css";
import "@perspective-dev/viewer/dist/css/pro-light.css";
import "@perspective-dev/viewer/dist/css/pro-dark.css";

// Other themes
Expand Down
Loading
Loading