website: snapshot

This commit is contained in:
nym21
2026-01-22 17:16:07 +01:00
parent a62a377081
commit 3c87d36535
6 changed files with 61 additions and 109 deletions
Generated
+2 -2
View File
@@ -1749,9 +1749,9 @@ dependencies = [
[[package]]
name = "importmap"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9995d103127daa52df1cd851c1bc9d8b023e4591ca4bdb47836b821499ab35f9"
checksum = "7e2d16f9503f27ae2f80f4914e81c98ab8e9921fe951e450936d1eaeaae9e8d0"
dependencies = [
"include_dir",
"rapidhash",
+1 -1
View File
@@ -11,7 +11,7 @@ include = ["src/**/*", "website/**/*", "examples/**/*", "Cargo.toml", "README.md
[dependencies]
axum = { workspace = true }
include_dir = "0.7"
importmap = { version = "0.3.0", features = ["embedded"] }
importmap = { version = "0.3.1", features = ["embedded"] }
tracing = { workspace = true }
[dev-dependencies]
+3 -6
View File
@@ -9,7 +9,7 @@ import {
import { createLegend } from "./legend.js";
import { capture } from "./capture.js";
import { colors } from "./colors.js";
import { createChoiceField } from "../utils/dom.js";
import { createRadios, createSelect } from "../utils/dom.js";
import { createPersistedValue } from "../utils/persisted.js";
import { onChange as onThemeChange } from "../utils/theme.js";
import { throttle, debounce } from "../utils/timing.js";
@@ -1289,7 +1289,7 @@ export function createChart({
paneIndex,
position: "sw",
createChild() {
const field = createChoiceField({
return createRadios({
choices: /** @type {const} */ (["lin", "log"]),
id: stringToId(`${id} ${paneIndex}`),
initialValue: persisted.value,
@@ -1298,8 +1298,6 @@ export function createChart({
applyScale(value);
},
});
return field;
},
});
}
@@ -1481,13 +1479,12 @@ export function createChart({
paneIndex,
position: "nw",
createChild() {
return createChoiceField({
return createSelect({
choices: units,
id: `pane-${paneIndex}-unit`,
initialValue: blueprints.panes[paneIndex].unit ?? defaultUnit,
toKey: (u) => u.id,
toLabel: (u) => u.name,
type: "select",
sorted: true,
onChange(unit) {
persistedUnit.set(unit.id);
+2 -2
View File
@@ -1,4 +1,4 @@
import { createShadow, createChoiceField, createHeader } from "../utils/dom.js";
import { createShadow, createRadios, createHeader } from "../utils/dom.js";
import { chartElement } from "../utils/elements.js";
import { serdeChartableIndex } from "../utils/serde.js";
import { Unit } from "../utils/units.js";
@@ -176,7 +176,7 @@ function createIndexSelector(chart) {
chart.index.name.set(currentValue);
}
field = createChoiceField({
field = createRadios({
initialValue: currentValue,
onChange: (v) => {
preferredIndex = v; // User explicitly selected, update preference
View File
+53 -98
View File
@@ -221,23 +221,15 @@ export function importStyle(href) {
* @param {(value: T) => void} [args.onChange]
* @param {(choice: T) => string} [args.toKey]
* @param {(choice: T) => string} [args.toLabel]
* @param {"radio" | "select"} [args.type]
* @param {boolean} [args.sorted]
*/
export function createChoiceField({
export function createRadios({
id,
choices: unsortedChoices,
choices,
initialValue,
onChange,
sorted,
toKey = /** @type {(choice: T) => string} */ ((/** @type {any} */ c) => c),
toLabel = /** @type {(choice: T) => string} */ ((/** @type {any} */ c) => c),
type = "radio",
}) {
const choices = sorted
? unsortedChoices.toSorted((a, b) => toLabel(a).localeCompare(toLabel(b)))
: unsortedChoices;
const field = window.document.createElement("div");
field.classList.add("field");
@@ -254,33 +246,6 @@ export function createChoiceField({
const span = window.document.createElement("span");
span.textContent = toLabel(choices[0]);
div.append(span);
} else if (type === "select") {
const select = window.document.createElement("select");
select.id = id ?? "";
select.name = id ?? "";
choices.forEach((choice) => {
const option = window.document.createElement("option");
option.value = toKey(choice);
option.textContent = toLabel(choice);
if (toKey(choice) === initialKey) {
option.selected = true;
}
select.append(option);
});
select.addEventListener("change", () => {
onChange?.(fromKey(select.value));
});
div.append(select);
const remaining = choices.length - 1;
if (remaining > 0) {
const small = window.document.createElement("small");
small.textContent = ` +${remaining}`;
field.append(small);
}
} else {
const fieldId = id ?? "";
choices.forEach((choice) => {
@@ -308,6 +273,57 @@ export function createChoiceField({
return field;
}
/**
* @template T
* @param {Object} args
* @param {T} args.initialValue
* @param {string} [args.id]
* @param {readonly T[]} args.choices
* @param {(value: T) => void} [args.onChange]
* @param {(choice: T) => string} [args.toKey]
* @param {(choice: T) => string} [args.toLabel]
* @param {boolean} [args.sorted]
*/
export function createSelect({
id,
choices: unsortedChoices,
initialValue,
onChange,
sorted,
toKey = /** @type {(choice: T) => string} */ ((/** @type {any} */ c) => c),
toLabel = /** @type {(choice: T) => string} */ ((/** @type {any} */ c) => c),
}) {
const choices = sorted
? unsortedChoices.toSorted((a, b) => toLabel(a).localeCompare(toLabel(b)))
: unsortedChoices;
const select = window.document.createElement("select");
select.id = id ?? "";
select.name = id ?? "";
const initialKey = toKey(initialValue);
/** @param {string} key */
const fromKey = (key) =>
choices.find((c) => toKey(c) === key) ?? initialValue;
choices.forEach((choice) => {
const option = window.document.createElement("option");
option.value = toKey(choice);
option.textContent = toLabel(choice);
if (toKey(choice) === initialKey) {
option.selected = true;
}
select.append(option);
});
select.addEventListener("change", () => {
onChange?.(fromKey(select.value));
});
return select;
}
/**
* @param {string} [title]
* @param {1 | 2 | 3} [level]
@@ -344,67 +360,6 @@ export function createOption(arg) {
return option;
}
/**
* @template {string} Name
* @template {string} Value
* @template {Value | {name: Name; value: Value}} T
* @param {Object} args
* @param {string} [args.id]
* @param {boolean} [args.deep]
* @param {readonly ((T) | {name: string; list: T[]})[]} args.list
* @param {Signal<T>} args.signal
*/
export function createSelect({ id, list, signal, deep = false }) {
const select = window.document.createElement("select");
if (id) {
select.name = id;
select.id = id;
}
/** @type {Record<string, VoidFunction>} */
const setters = {};
list.forEach((anyOption, index) => {
if (typeof anyOption === "object" && "list" in anyOption) {
const { name, list } = anyOption;
const optGroup = window.document.createElement("optgroup");
optGroup.label = name;
select.append(optGroup);
list.forEach((option) => {
optGroup.append(createOption(option));
const key = /** @type {string} */ (
typeof option === "object" ? option.value : option
);
setters[key] = () => signal.set(() => option);
});
} else {
select.append(createOption(anyOption));
const key = /** @type {string} */ (
typeof anyOption === "object" ? anyOption.value : anyOption
);
setters[key] = () => signal.set(() => anyOption);
}
if (deep && index !== list.length - 1) {
select.append(window.document.createElement("hr"));
}
});
select.addEventListener("change", () => {
const callback = setters[select.value];
// @ts-ignore
if (callback) {
callback();
}
});
const initialSignal = signal();
const initialValue =
typeof initialSignal === "object" ? initialSignal.value : initialSignal;
select.value = String(initialValue);
return { select, signal };
}
/**
* @param {Object} args