/ Examples
treesearchselectionautosize

Code Explorer

Browse vlist's own source: a virtualized file tree on the left, a searchable list on the right. Pick a file to list its exported symbols (functions, constants, types…) and filter them; or switch to History to scrub the commits that touched that file in navigate mode. clears.

Code Explorer

vlist
0% 0.00 / 0.00 px/ms 0 / 0 rendered
0 / 0 matches
Source
// Code Explorer — browse vlist's own source with two coordinated virtual lists.
//   • Left:  a file tree (tree plugin) of the vlist source, with Zed-style icons.
//   • Center: a vlist + search showing either the selected file's exported
//             symbols (filter) or its commit history (navigate). With no file
//             selected, History shows the full git log.
//   • Right: a controls panel demonstrating the search plugin's configuration.

import { createVList, tree, search, selection, autosize, scrollbar } from "vlist";
import { VLIST_TREE } from "../../src/data/vlist-tree.js";
import { VLIST_HISTORY } from "../../src/data/vlist-history.js";
import { getIcon, getChevron } from "../tree/icons.js";
import { createStats } from "../stats.js";
import { createInfoUpdater } from "../info.js";

// =============================================================================
// Derived data
// =============================================================================

const SYMBOL_KINDS = new Set(["function", "const", "class", "interface", "type", "enum", "re-export"]);
const esc = (s) =>
  String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
const basename = (p) => p.split("/").pop();

function timeAgo(dateStr) {
  const sec = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000);
  if (sec < 60) return "just now";
  const min = Math.floor(sec / 60);
  if (min < 60) return `${min}m ago`;
  const hr = Math.floor(min / 60);
  if (hr < 24) return `${hr}h ago`;
  const day = Math.floor(hr / 24);
  if (day < 30) return `${day}d ago`;
  const mo = Math.floor(day / 30);
  if (mo < 12) return `${mo}mo ago`;
  return `${Math.floor(mo / 12)}y ago`;
}

const KW = new Set([
  "export", "function", "const", "let", "var", "interface", "type", "class",
  "enum", "extends", "implements", "readonly", "async", "abstract", "static",
  "declare", "import", "from", "new", "return", "default", "of", "in",
]);
const TYPES = new Set([
  "string", "number", "boolean", "void", "null", "undefined", "any",
  "unknown", "never", "true", "false", "Promise", "Array", "Record",
  "Set", "Map", "Partial", "Required", "Omit", "Pick",
]);
const TOKEN_RE = /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)|(\b[A-Za-z_$][\w$]*\b)|(\d+(?:\.\d+)?)|([<>()[\]{};:,=|&?.]|=>|\.\.\.)/g;

function syntaxHighlight(raw) {
  let out = "";
  let last = 0;
  let m;
  TOKEN_RE.lastIndex = 0;
  while ((m = TOKEN_RE.exec(raw)) !== null) {
    if (m.index > last) out += esc(raw.slice(last, m.index));
    const [tok, str, ident, num, punct] = m;
    const e = esc(tok);
    if (str) out += `<span class="syn-str">${e}</span>`;
    else if (num) out += `<span class="syn-num">${e}</span>`;
    else if (punct) out += `<span class="syn-punct">${e}</span>`;
    else if (ident && KW.has(ident)) out += `<span class="syn-kw">${e}</span>`;
    else if (ident && TYPES.has(ident)) out += `<span class="syn-type">${e}</span>`;
    else out += e;
    last = m.index + tok.length;
  }
  if (last < raw.length) out += esc(raw.slice(last));
  return out;
}

const TREE_NODES = VLIST_TREE.filter((n) => n.kind === "dir" || n.kind === "file");

// All symbols indexed by their parent file.
const ALL_SYMBOLS = VLIST_TREE.filter((n) => SYMBOL_KINDS.has(n.kind))
  .sort((a, b) => (b.exported ? 1 : 0) - (a.exported ? 1 : 0));
const symbolsByFile = new Map();
for (const n of ALL_SYMBOLS) {
  const arr = symbolsByFile.get(n.parentId) ?? [];
  arr.push(n);
  symbolsByFile.set(n.parentId, arr);
}
for (const [, arr] of symbolsByFile) {
  arr.sort((a, b) => (b.exported ? 1 : 0) - (a.exported ? 1 : 0));
}

// All file paths (for dir → files lookup).
const ALL_FILES = VLIST_TREE.filter((n) => n.kind === "file").map((n) => n.id);

// Collect symbols for a selection: null → all, file → its symbols, dir → all files under it.
function symbolsFor(nodeId) {
  if (!nodeId) return ALL_SYMBOLS;
  if (symbolsByFile.has(nodeId)) return symbolsByFile.get(nodeId);
  // Directory — collect from all files whose path starts with this dir.
  const prefix = nodeId + "/";
  const syms = [];
  for (const file of ALL_FILES) {
    if (file === nodeId || file.startsWith(prefix)) {
      const s = symbolsByFile.get(file);
      if (s) syms.push(...s);
    }
  }
  return syms;
}

// Collect commits for a selection: null → all, file → its commits, dir → commits touching any file under it.
function commitsFor(nodeId) {
  if (!nodeId) return VLIST_HISTORY;
  const prefix = nodeId + "/";
  return VLIST_HISTORY.filter((c) =>
    c.files.some((f) => f === nodeId || f.startsWith(prefix)),
  );
}

// =============================================================================
// Configurable state
// =============================================================================

let treeList = null;
let rightList = null;
let selectedFile = null;
let rightView = "symbols"; // "symbols" | "history"

// Search config (driven by control panel)
let searchEnabled = true;
let searchMode = "filter";
let searchVariant = "default";
let highlightEnabled = true;
let highlightScoped = false;
let caseSensitive = false;

// =============================================================================
// Stats
// =============================================================================

export const stats = createStats({
  getScrollPosition: () => rightList?.getScrollPosition() ?? 0,
  getTotal: () => rightList?.total ?? 0,
  getItemSize: () => (rightView === "history" ? 56 : 52),
  getContainerSize: () => document.querySelector("#symbol-container")?.clientHeight ?? 0,
});
const updateInfo = createInfoUpdater(stats);

// =============================================================================
// Left pane — file tree
// =============================================================================

const renderTreeNode = (item, _index, state) => {
  const t = state.tree ?? {};
  const isFolder = item.kind === "dir";
  const count = isFolder ? 0 : symbolsFor(item.id).length;
  const meta = count ? `<span class="tree-node__meta">${count}</span>` : "";
  return `
    <div class="tree-node tree-node--${item.kind}">
      <span class="tree-node__icon">${getIcon(item.name, isFolder, t.expanded)}</span>
      <span class="tree-node__label">${esc(item.name)}</span>
      ${meta}
    </div>
  `;
};

function buildTree() {
  treeList = createVList(
    {
      container: "#tree-container",
      ariaLabel: "vlist source files",
      item: { height: 30, template: renderTreeNode },
      items: TREE_NODES,
    },
    [
      tree({
        parentId: "parentId",
        label: "name",
        indent: 18,
        expanded: ["src"],
        expandOnClick: true,
        connectorLines: true,
      }),
      selection({ mode: "single", followFocus: true, focusOnClick: true }),
      scrollbar({ autoHide: false, padding: 0 }),
    ],
  );

  const onTreeSelect = (id) => {
    if (id !== selectedFile) {
      selectedFile = id;
      buildRight();
    }
  };

  treeList.on("selection:change", ({ items }) => {
    if (items[0]) onTreeSelect(items[0].id);
  });
  treeList.on("tree:expand", ({ id }) => { treeList.select(id); onTreeSelect(id); });
  treeList.on("tree:collapse", ({ id }) => { treeList.select(id); onTreeSelect(id); });
}

// =============================================================================
// Right pane — symbols (filter) | history (navigate)
// =============================================================================

const renderSymbol = (item) => `
  <div class="sym sym--${item.kind}${item.exported ? "" : " sym--internal"}">
    <div class="sym__row">
      <span class="sym__kind">${item.kind}</span>
      <span class="sym__name">${esc(item.name)}</span>
    </div>
    ${item.signature ? `<pre class="sym__sig">${syntaxHighlight(item.signature)}</pre>` : ""}
  </div>
`;

const renderCommit = (item) => `
  <div class="commit commit--${item.type}">
    <div class="commit__left">
      <span class="commit__type">${item.type}</span>
      ${item.tag ? `<span class="commit__tag">${esc(item.tag)}</span>` : ""}
    </div>
    <div class="commit__body">
      <div class="commit__subject">${esc(item.subject)}</div>
      <div class="commit__meta">
        <code>${item.shortHash}</code> · ${esc(item.author)} · <span class="commit__ago">${timeAgo(item.date)}</span>
      </div>
    </div>
  </div>
`;

function showEmpty(message) {
  const c = document.getElementById("symbol-container");
  if (rightList) {
    rightList.destroy();
    rightList = null;
  }
  c.innerHTML = `<div class="explorer-empty">${esc(message)}</div>`;
  updateInfo();
  updateMatchInfo(0, 0);
}

function resolveHighlight() {
  if (!highlightEnabled) return false;
  if (!highlightScoped) return true;
  return rightView === "symbols"
    ? { within: ".sym__name" }
    : { within: ".commit__subject" };
}

function buildRight() {
  updateContext();
  const prevQuery = rightList?.getQuery?.() ?? "";
  if (rightList) {
    rightList.destroy();
    rightList = null;
  }
  const c = document.getElementById("symbol-container");
  c.innerHTML = "";

  const isSymbols = rightView === "symbols";
  const items = isSymbols ? symbolsFor(selectedFile) : commitsFor(selectedFile);

  if (isSymbols && !items.length) {
    return showEmpty(selectedFile ? `No exported symbols in ${basename(selectedFile)}` : "No symbols found");
  }
  if (!isSymbols && !items.length) {
    return showEmpty(selectedFile ? `No commits touched ${basename(selectedFile)}` : "No commits");
  }

  const label = selectedFile ? basename(selectedFile) : "vlist";
  const mode = searchMode;
  const placeholderText = isSymbols
    ? `${searchMode === "filter" ? "Filter" : "Find"} symbols in ${label}…`
    : `Search ${selectedFile ? "history of " + label : "all commits"}…`;

  const plugins = [];
  if (searchEnabled) {
    plugins.push(
      search({
        mode,
        text: { placeholder: placeholderText },
        field: isSymbols
          ? "name"
          : (c) => `${c.subject} ${c.author} ${c.shortHash}`,
        highlight: resolveHighlight(),
        caseSensitive,
        variant: searchVariant,
      }),
    );
  }
  plugins.push(autosize());
  plugins.push(scrollbar({ autoHide: false, padding: 0 }));
  plugins.push(selection({ mode: "single" }));

  rightList = createVList(
    {
      container: "#symbol-container",
      ariaLabel: isSymbols ? `Symbols in ${label}` : `${selectedFile ? "History of " + label : "All commits"}`,
      item: { height: isSymbols ? 52 : 56, template: isSymbols ? renderSymbol : renderCommit },
      items,
    },
    plugins,
  );

  wireRight(items.length);
  reflectControls();

  if (prevQuery && searchEnabled) {
    rightList.setQuery?.(prevQuery);
  }
}

function wireRight(total) {
  rightList.on("scroll", updateInfo);
  rightList.on("range:change", updateInfo);
  rightList.on("velocity:change", ({ velocity }) => {
    stats.onVelocity(velocity);
    updateInfo();
  });
  if (searchEnabled) {
    rightList.on("search:change", ({ matches, total: t }) => {
      updateMatchInfo(matches, t);
      updateInfo();
    });
    rightList.on("search:match", ({ matchIndex, matches }) => {
      updateMatchInfo(matches, rightList?.total ?? 0, matchIndex);
    });
  }
  updateInfo();
  updateMatchInfo(0, total);
}

// =============================================================================
// Toolbar
// =============================================================================

function updateContext() {
  const el = document.getElementById("cx-context");
  if (!el) return;
  el.innerHTML = selectedFile
    ? `<code>${esc(selectedFile)}</code>`
    : `<span class="explorer-context-all">All files</span>`;
}

function setActiveView(view) {
  document
    .getElementById("cx-view-toggle")
    ?.querySelectorAll("button")
    .forEach((b) => b.classList.toggle("ui-segmented__btn--active", b.dataset.value === view));
}

function updateMatchInfo(matches, total, currentIndex) {
  const el = document.getElementById("info-matches");
  if (!el) return;
  if (!searchEnabled) {
    el.textContent = "off";
    return;
  }
  if (rightView === "history" && matches > 0 && currentIndex != null) {
    el.textContent = `${currentIndex + 1} / ${matches}`;
  } else {
    el.textContent = `${matches} / ${total}`;
  }
}

function reflectControls() {
  const off = !searchEnabled;
  const isHistory = rightView === "history";
  for (const id of ["cx-mode-section", "cx-style-section", "cx-highlight-section", "cx-options-section"]) {
    const el = document.getElementById(id);
    if (el) el.classList.toggle("ui-section--disabled", off);
  }
  const activeMode = searchMode;
  document.getElementById("cx-mode-toggle")
    ?.querySelectorAll("button")
    .forEach((b) => b.classList.toggle("ui-segmented__btn--active", b.dataset.value === activeMode));
}

// =============================================================================
// Boot + controls (all ids prefixed cx- to avoid shell collision)
// =============================================================================

buildTree();
buildRight();

// View toggle: Symbols | History — clear the query when switching views
// since the data is entirely different.
document.getElementById("cx-view-toggle")?.addEventListener("click", (e) => {
  const btn = e.target.closest("[data-value]");
  if (!btn || btn.dataset.value === rightView) return;
  if (rightList?.getQuery?.()) rightList.setQuery?.("");
  rightView = btn.dataset.value;
  setActiveView(rightView);
  buildRight();
});

// Tree expand / collapse
let treeExpanded = false;
const treeToggle = document.getElementById("cx-tree-toggle");
treeToggle?.addEventListener("click", () => {
  treeExpanded = !treeExpanded;
  if (treeExpanded) treeList?.expandAll?.();
  else treeList?.collapseAll?.();
  treeToggle.textContent = treeExpanded ? "Collapse all" : "Expand all";
});

// Search enable
document.getElementById("cx-search-toggle")?.addEventListener("change", (e) => {
  searchEnabled = e.target.checked;
  buildRight();
});

// Mode: filter / navigate
wireSegment("cx-mode-toggle", (v) => { searchMode = v; buildRight(); });

// Bar style: default / md3
wireSegment("cx-style-toggle", (v) => { searchVariant = v; buildRight(); });

// Highlight enable
document.getElementById("cx-highlight-toggle")?.addEventListener("change", (e) => {
  highlightEnabled = e.target.checked;
  buildRight();
});

// Highlight scope: name only / whole row
wireSegment("cx-scope-toggle", (v) => { highlightScoped = v === "scoped"; buildRight(); });

// Case sensitive
document.getElementById("cx-case-toggle")?.addEventListener("change", (e) => {
  caseSensitive = e.target.checked;
  buildRight();
});

function wireSegment(id, setter) {
  const el = document.getElementById(id);
  if (!el) return;
  el.addEventListener("click", (e) => {
    const btn = e.target.closest("[data-value]");
    if (!btn) return;
    el.querySelectorAll("button").forEach((b) =>
      b.classList.toggle("ui-segmented__btn--active", b === btn),
    );
    setter(btn.dataset.value);
  });
}
/* Code Explorer — file tree (left) + searchable symbols/history (right). */

.split-main.split-main--full {
    height: 580px;
}

.ui-section--disabled {
    opacity: 0.4;
    pointer-events: none;
}

/* ── Explorer layout inside split-main ─────────────────────────────────── */
.explorer {
    display: flex;
    gap: 0;
    height: 100%;
}

.explorer-tree {
    flex: 0 0 260px;
    display: flex;
    flex-direction: column;
    border: 1px solid var(--vlist-border, #e5e7eb);
    border-right: none;
    border-radius: 10px 0 0 10px;
    overflow: hidden;
}

.explorer-tree .tree-node--file .tree-node__label {
    font-size: 15px;
}

.explorer-tree .vlist-content {
    font-family: var(--font-mono);
}

.explorer-list {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    border: 1px solid var(--vlist-border, #e5e7eb);
    border-radius: 0 10px 10px 0;
    overflow: hidden;
}

.explorer-list .vlist-content {
    font-family: var(--font-mono);
}

.explorer-list .vlist-content .vlist-item {
    font-size: 0.8em;
}

#tree-container,
#symbol-container {
    flex: 1 1 auto;
    min-height: 0;
}

.explorer-tree .vlist,
.explorer-list .vlist {
    border: none;
    border-radius: 0;
}

/* Custom scrollbar plugin — always visible, flat. */
.explorer-tree .vlist {
    --vlist-custom-scrollbar-width: 4px;
    --vlist-custom-scrollbar-radius: 0;
}

.explorer-list .vlist {
    --vlist-custom-scrollbar-width: 16px;
    --vlist-custom-scrollbar-radius: 0;
}

.explorer-header {
    flex: none;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 5px 8px 5px 12px;
    border-bottom: 1px solid var(--vlist-border, #e5e7eb);
}

.explorer-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--vlist-text, #111827);
}

.explorer-btn {
    flex: 0 0 auto;
    padding: 3px 8px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--vlist-text-muted, #94a3b8);
    font: inherit;
    font-size: 11px;
    cursor: pointer;
}

.explorer-btn:hover {
    background: var(--vlist-bg-hover, rgba(127, 127, 127, 0.12));
    color: var(--vlist-text, #111827);
}

.explorer-context {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 12px;
}

.explorer-context code {
    font-size: 12px;
}

.explorer-context-all {
    color: var(--vlist-text-muted, #94a3b8);
}

.explorer-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    padding: 24px;
    text-align: center;
    color: var(--vlist-text-muted, #94a3b8);
    font-size: 13px;
}

/* ── File tree row (shared with the Tree View example) ─────────────────── */
.tree-node {
    display: flex;
    align-items: center;
    gap: 4px;
    height: 100%;
    width: 100%;
    padding-right: 14px;
    cursor: default;
    user-select: none;
}

.tree-node__chevron {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    margin-left: 2px;
    flex-shrink: 0;
    border-radius: 3px;
}

.tree-node__chevron:hover {
    color: var(--vlist-text, #111827);
    background: var(--vlist-bg-hover, rgba(0, 0, 0, 0.06));
}

.tree-node__chevron--leaf {
    visibility: hidden;
}

.tree-node__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    flex-shrink: 0;
    overflow: visible;
}

.tree-node__icon svg {
    overflow: visible;
}

.tree-node__label {
    flex: 1;
    min-width: 0;
    font-size: 15px;
    font-weight: 500;
    color: var(--vlist-text, #111827);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tree-node--dir .tree-node__label {
    font-weight: 500;
}

.tree-node--file .tree-node__label {
    font-weight: 500;
}

.tree-node__meta {
    font-size: 11px;
    color: var(--vlist-text-muted, #94a3b8);
    font-variant-numeric: tabular-nums;
    flex-shrink: 0;
}

/* ── Symbol row ────────────────────────────────────────────────────────── */
.sym {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    padding: 8px 12px;
    box-sizing: border-box;
}

.sym__row {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.sym__kind {
    flex: 0 0 auto;
    padding: 1px 6px;
    border-radius: 5px;
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    background: var(--vlist-bg-hover, rgba(127, 127, 127, 0.14));
    color: var(--vlist-text-muted, #94a3b8);
}

.sym__name {
    min-width: 0;
    font-size: 10px;
    font-weight: 600;
    color: var(--vlist-text, #e5e7eb);
}

.sym__sig {
    margin: 0;
    padding: 0;
    font-family: inherit;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
    color: var(--vlist-text-muted, #94a3b8);
}

/* ── Syntax highlighting (One Dark / One Light) ───────────────────────── */
.syn-kw {
    color: #c678dd;
}
.syn-type {
    color: #e6c07b;
}
.syn-str {
    color: #98c379;
}
.syn-num {
    color: #d19a66;
}
.syn-punct {
    color: #abb2bf;
}

[data-theme-mode="light"] .syn-kw {
    color: #a626a4;
}
[data-theme-mode="light"] .syn-type {
    color: #c18401;
}
[data-theme-mode="light"] .syn-str {
    color: #50a14f;
}
[data-theme-mode="light"] .syn-num {
    color: #986801;
}
[data-theme-mode="light"] .syn-punct {
    color: #383a42;
}

.sym--internal {
    opacity: 0.6;
}

.sym--function .sym__kind {
    color: #38bdf8;
}
.sym--const .sym__kind {
    color: #a78bfa;
}
.sym--class .sym__kind {
    color: #fb923c;
}
.sym--interface .sym__kind {
    color: #34d399;
}
.sym--type .sym__kind {
    color: #f472b6;
}
.sym--enum .sym__kind {
    color: #facc15;
}
.sym--re-export .sym__kind {
    color: #94a3b8;
}

/* ── Commit row ────────────────────────────────────────────────────────── */
.commit {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 8px 12px;
    box-sizing: border-box;
}

.commit__left {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    min-width: 66px;
}

.commit__type {
    width: 100%;
    padding: 2px 7px;
    border-radius: 6px;
    text-align: center;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    background: var(--vlist-bg-hover, rgba(127, 127, 127, 0.14));
    color: var(--vlist-text-muted, #94a3b8);
    box-sizing: border-box;
}

.commit__body {
    min-width: 0;
}

.commit__subject {
    font-size: 13px;
    line-height: 1.4;
    color: var(--vlist-text, #e5e7eb);
}

.commit__meta {
    margin-top: 3px;
    font-size: 11px;
    color: var(--vlist-text-muted, #94a3b8);
}

.commit__meta code {
    font-size: 11px;
}

.commit__tag {
    padding: 1px 6px;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.4;
    text-align: center;
    color: #a78bfa;
    background: rgba(167, 139, 250, 0.12);
    border: 1px solid rgba(167, 139, 250, 0.25);
    border-radius: 4px;
}

.commit--feat .commit__type {
    color: #34d399;
}
.commit--fix .commit__type {
    color: #f87171;
}
.commit--docs .commit__type {
    color: #38bdf8;
}
.commit--refactor .commit__type {
    color: #a78bfa;
}
.commit--perf .commit__type {
    color: #22d3ee;
}
.commit--test .commit__type {
    color: #facc15;
}
.commit--style .commit__type {
    color: #f472b6;
}
.commit--chore .commit__type,
.commit--ci .commit__type {
    color: #94a3b8;
}
.commit--revert .commit__type {
    color: #fb923c;
}
<div class="container">
    <header>
        <h1>Code Explorer</h1>
        <p class="description">
            Browse <strong>vlist's own source</strong>: a virtualized file
            <code>tree</code> on the left, a searchable list on the right. Pick
            a file to list its exported <strong>symbols</strong> (functions,
            constants, types…) and filter them; or switch to
            <strong>History</strong> to scrub the commits that touched that file
            in <em>navigate</em> mode. clears.
        </p>
    </header>

    <div class="split-layout">
        <div class="split-main split-main--full">
            <h2 class="sr-only">Code Explorer</h2>
            <div class="explorer">
                <div class="explorer-tree">
                    <div class="explorer-header">
                        <span class="explorer-title">vlist</span>
                        <button
                            class="explorer-btn"
                            id="cx-tree-toggle"
                            type="button"
                        >
                            Expand all
                        </button>
                    </div>
                    <div id="tree-container"></div>
                </div>

                <div class="explorer-list">
                    <div class="explorer-header">
                        <span class="explorer-context" id="cx-context"></span>
                        <div class="ui-segmented" id="cx-view-toggle">
                            <button
                                class="ui-segmented__btn ui-segmented__btn--active"
                                data-value="symbols"
                            >
                                Symbols
                            </button>
                            <button
                                class="ui-segmented__btn"
                                data-value="history"
                            >
                                History
                            </button>
                        </div>
                    </div>
                    <div id="symbol-container"></div>
                </div>
            </div>
        </div>

        <aside class="split-panel">
            <!-- Search config -->
            <section class="ui-section">
                <h3 class="ui-title">Search</h3>
                <div class="ui-row">
                    <label class="ui-label" for="cx-search-toggle"
                        >Enable</label
                    >
                    <label class="ui-switch">
                        <input type="checkbox" id="cx-search-toggle" checked />
                        <span class="ui-switch__track"></span>
                    </label>
                </div>
            </section>

            <!-- Mode -->
            <section class="ui-section" id="cx-mode-section">
                <h3 class="ui-title">Mode</h3>
                <div class="ui-row">
                    <div class="ui-segmented" id="cx-mode-toggle">
                        <button
                            class="ui-segmented__btn ui-segmented__btn--active"
                            data-value="filter"
                        >
                            Filter
                        </button>
                        <button class="ui-segmented__btn" data-value="navigate">
                            Navigate
                        </button>
                    </div>
                </div>
            </section>

            <!-- Variant -->
            <section class="ui-section" id="cx-style-section">
                <h3 class="ui-title">Variant</h3>
                <div class="ui-row">
                    <div class="ui-segmented" id="cx-style-toggle">
                        <button
                            class="ui-segmented__btn ui-segmented__btn--active"
                            data-value="default"
                        >
                            Default
                        </button>
                        <button class="ui-segmented__btn" data-value="md3">
                            MD3
                        </button>
                    </div>
                </div>
            </section>

            <!-- Highlight -->
            <section class="ui-section" id="cx-highlight-section">
                <h3 class="ui-title">Highlight</h3>
                <div class="ui-row">
                    <label class="ui-label" for="cx-highlight-toggle"
                        >Enable</label
                    >
                    <label class="ui-switch">
                        <input
                            type="checkbox"
                            id="cx-highlight-toggle"
                            checked
                        />
                        <span class="ui-switch__track"></span>
                    </label>
                </div>
                <div class="ui-row">
                    <div class="ui-segmented" id="cx-scope-toggle">
                        <button class="ui-segmented__btn" data-value="scoped">
                            Name only
                        </button>
                        <button
                            class="ui-segmented__btn ui-segmented__btn--active"
                            data-value="full"
                        >
                            Whole row
                        </button>
                    </div>
                </div>
            </section>

            <!-- Options -->
            <section class="ui-section" id="cx-options-section">
                <h3 class="ui-title">Options</h3>
                <div class="ui-row">
                    <label class="ui-label" for="cx-case-toggle"
                        >Case sensitive</label
                    >
                    <label class="ui-switch">
                        <input type="checkbox" id="cx-case-toggle" />
                        <span class="ui-switch__track"></span>
                    </label>
                </div>
            </section>
        </aside>
    </div>

    <div class="example-info" id="example-info">
        <div class="example-info__left">
            <span class="example-info__stat">
                <strong id="info-progress">0%</strong>
            </span>
            <span class="example-info__stat">
                <span id="info-velocity">0.00</span> /
                <strong id="info-velocity-avg">0.00</strong>
                <span class="example-info__unit">px/ms</span>
            </span>
            <span class="example-info__stat">
                <span id="info-dom">0</span> /
                <strong id="info-total">0</strong>
                <span class="example-info__unit">rendered</span>
            </span>
        </div>
        <div class="example-info__right">
            <span class="example-info__stat">
                <strong id="info-matches">0 / 0</strong>
                <span class="example-info__unit">matches</span>
            </span>
        </div>
    </div>
</div>