Skip to the content

Capsid menu

components/permission-matrix/permission-matrix.ts

71 lines and 14 functions in DrDustinEdwards/capsomer, at commit d3966cc on 2026-10-24. This file on GitHub.

Functions

The functions of permission-matrix.ts
FunctionLinesComplexityCoveredCRAPMutation
holdings32 to 371No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
<anonymous:33>33 to 362No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
<anonymous:34>34 to 341No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
holders40 to 451No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
<anonymous:41>41 to 442No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
<anonymous:42>42 to 421No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
<anonymous:42>42 to 421No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
showView49 to 553No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
isView57 to 592No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
enhance63 to 824No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
onChange70 to 744No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
<anonymous:76>76 to 791No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
<anonymous:81>81 to 811No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
<anonymous:81>81 to 811No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet

Imports

It imports no other module of this repo. Imported by components/permission-matrix/permission-matrix.react.tsx.

Source

// The permission matrix's behaviour: the view switch (By agent, By permission), and the
// pure helpers that turn a matrix into each view's sentences. No framework; the React
// wrapper uses the same helpers.

export type PermView = "agent" | "permission";

export interface PermAgent {
  name: string;
  // The permissions this agent holds, by name.
  holds: readonly string[];
}

export interface PermMatrix {
  // The permissions, in the order the columns show them.
  permissions: readonly string[];
  agents: readonly PermAgent[];
}

export interface Holding {
  agent: string;
  held: string[];
  text: string;
}

export interface Holders {
  permission: string;
  agents: string[];
  text: string;
}

// One entry per agent: what it holds, in column order, and the card's sentence.
export function holdings(m: PermMatrix): Holding[] {
  return m.agents.map((a) => {
    const held = m.permissions.filter((p) => a.holds.includes(p));
    return { agent: a.name, held, text: held.length ? `Holds: ${held.join(", ")}` : "Holds no permissions" };
  });
}

// One entry per permission: who holds it, in row order, and the card's sentence.
export function holders(m: PermMatrix): Holders[] {
  return m.permissions.map((p) => {
    const agents = m.agents.filter((a) => a.holds.includes(p)).map((a) => a.name);
    return { permission: p, agents, text: agents.length ? `Held by: ${agents.join(", ")}` : "Nobody holds this" };
  });
}

// Shows one view of one matrix and hides the other with the hidden attribute, so the
// hidden view leaves the accessibility tree too.
export function showView(root: HTMLElement, view: PermView): void {
  for (const el of root.querySelectorAll<HTMLElement>(".cap-perms-view[data-view]")) {
    if (el.closest("[data-cap='permission-matrix']") !== root) continue;
    el.hidden = el.dataset.view !== view;
  }
  root.dataset.current = view;
}

function isView(v: string): v is PermView {
  return v === "agent" || v === "permission";
}

// Attaches to every [data-cap="permission-matrix"] under root that is not attached yet.
// Returns a function that detaches them all.
export function enhance(root: ParentNode = document): () => void {
  const undo: Array<() => void> = [];
  for (const matrix of root.querySelectorAll<HTMLElement>("[data-cap='permission-matrix']:not([data-cap-ready])")) {
    matrix.dataset.capReady = "";
    const checked = matrix.querySelector<HTMLInputElement>(".cap-perms-bar input[type='radio']:checked");
    if (checked && isView(checked.value)) showView(matrix, checked.value);

    const onChange = (e: Event) => {
      const input = e.target as HTMLInputElement;
      if (input.type !== "radio" || !input.closest(".cap-perms-bar") || !isView(input.value)) return;
      showView(matrix, input.value);
    };
    matrix.addEventListener("change", onChange);
    undo.push(() => {
      matrix.removeEventListener("change", onChange);
      delete matrix.dataset.capReady;
    });
  }
  return () => undo.forEach((f) => f());
}