diff --git a/Cargo.lock b/Cargo.lock index 853c4f974..bbe039ee0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/crates/brk_website/Cargo.toml b/crates/brk_website/Cargo.toml index 9622f3ce6..c2a21ff99 100644 --- a/crates/brk_website/Cargo.toml +++ b/crates/brk_website/Cargo.toml @@ -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] diff --git a/website/scripts/chart/index.js b/website/scripts/chart/index.js index b8baa3cb3..a7563b1b4 100644 --- a/website/scripts/chart/index.js +++ b/website/scripts/chart/index.js @@ -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); diff --git a/website/scripts/panes/chart.js b/website/scripts/panes/chart.js index d247531f9..03da5ee05 100644 --- a/website/scripts/panes/chart.js +++ b/website/scripts/panes/chart.js @@ -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 diff --git a/website/scripts/panes/nav.js b/website/scripts/panes/nav.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/website/scripts/utils/dom.js b/website/scripts/utils/dom.js index 7052e5163..603f4bf3f 100644 --- a/website/scripts/utils/dom.js +++ b/website/scripts/utils/dom.js @@ -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} 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} */ - 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