v2.0.0

<bmx-permission-matrix>

Who may do what, on one screen: resources down the side (as a tree), roles across the top with their actions under them, and in each cell whether that role may take that action on that resource - and why.

11 properties · 2 events · 12 methods · 10 parts

Example

Click a cell to cycle it: inherit, allow, deny. Shift+click selects a range for the toolbar. Managers inherit from Staff; Finance leads from Managers and Auditors.

Show markup
<div class="row" style="display: block">
  <bmx-permission-matrix id="ex-pm" label="Finance permissions" style="--bmx-permission-matrix-height: 32rem"></bmx-permission-matrix>
</div>
<p class="note" id="ex-pm-out" role="status">Click a cell to cycle it: inherit, allow, deny. Shift+click selects a range for the toolbar. Managers inherit from Staff; Finance leads from Managers and Auditors.</p>

<script type="module">
  await customElements.whenDefined('bmx-permission-matrix');
  const pm = document.getElementById('ex-pm');
  const out = document.getElementById('ex-pm-out');
  pm.roles = [
    { id: 'staff', label: 'Staff', description: 'Everyone in the company' },
    { id: 'manager', label: 'Manager', inherits: ['staff'] },
    { id: 'auditor', label: 'Auditor', description: 'External audit, read only' },
    { id: 'lead', label: 'Finance lead', inherits: ['manager', 'auditor'] },
  ];
  pm.actions = [
    { id: 'read', label: 'Read' },
    { id: 'create', label: 'Create' },
    { id: 'approve', label: 'Approve' },
    { id: 'export', label: 'Export' },
  ];
  pm.resources = [
    { id: 'finance', label: 'Finance' },
    { id: 'invoices', label: 'Invoices', parent: 'finance' },
    { id: 'credit-notes', label: 'Credit notes', parent: 'invoices' },
    { id: 'payments', label: 'Payments', parent: 'finance' },
    { id: 'payroll', label: 'Payroll', parent: 'finance', description: 'Salaries: read and export only', actions: ['read', 'export'] },
    { id: 'reports', label: 'Reports' },
    { id: 'board-pack', label: 'Board pack', parent: 'reports' },
    { id: 'settings', label: 'Settings' },
    { id: 'tax-rates', label: 'Tax rates', parent: 'settings' },
    { id: 'users', label: 'Users', parent: 'settings' },
  ];
  pm.value = [
    { role: 'staff', resource: 'finance', action: 'read', effect: 'allow' },
    { role: 'staff', resource: 'payroll', action: 'read', effect: 'deny' },
    { role: 'staff', resource: 'reports', action: 'read', effect: 'allow' },
    { role: 'manager', resource: 'invoices', action: 'create', effect: 'allow' },
    { role: 'manager', resource: 'invoices', action: 'approve', effect: 'allow' },
    { role: 'manager', resource: 'reports', action: 'export', effect: 'allow' },
    { role: 'auditor', resource: 'finance', action: 'read', effect: 'allow' },
    { role: 'auditor', resource: 'finance', action: 'export', effect: 'allow' },
    { role: 'auditor', resource: 'invoices', action: 'approve', effect: 'deny' },
    { role: 'lead', resource: 'payroll', action: 'read', effect: 'allow' },
    { role: 'lead', resource: 'settings', action: 'create', effect: 'allow' },
  ];
  pm.addEventListener('bmxPermissionSave', e => {
    out.textContent = `Saved ${e.detail.changes.length} change(s); ${e.detail.grants.length} explicit grants in all.`;
  });
</script>
<bmx-permission-matrix id="perms"></bmx-permission-matrix>
<script>
  perms.roles = [{ id: 'staff' }, { id: 'manager', inherits: ['staff'] }];
  perms.actions = [{ id: 'read' }, { id: 'approve' }];
  perms.resources = [{ id: 'finance' }, { id: 'invoices', parent: 'finance' }];
  perms.value = [{ role: 'staff', resource: 'finance', action: 'read', effect: 'allow' }];
  perms.addEventListener('bmxPermissionSave', e => api.save(e.detail.grants));
</script>

A cell is set to allow or deny, or left to inherit: from the resource's parents ("Finance" covers "Finance > Invoices"), then from the roles the role inherits from (deny-overrides by default when they disagree), then the default. Each cell shows the effective answer - filled when set there, plain when inherited - and says why, in its name and in the line under the matrix.

Clicking a cell cycles it (inherit, allow, deny); Shift extends a selection and the toolbar sets every selected cell at once; a role's or an action's header selects its column. Changes are kept apart from the saved state until Save: they are counted, marked, listed under Review (each one revertible), undoable, and bmxPermissionSave hands the page the grants and the differences. Only the rows in view are drawn, so thousands of resources are fine. Export writes the effective matrix as CSV, for an audit.

The matrix is a treegrid: rows are resources (expandable), cells are named "Manager, Approve: Allowed - inherited from Staff on Finance"; arrows, Home, End, Page Up/Down and Ctrl+Home/End move; Space cycles, A allows, D denies, I or Delete inherits; Shift+arrows extend; Ctrl+A selects all; Ctrl+Z and Ctrl+Y undo and redo; Right/Left on a resource open and close it.

Properties

PropertyAttributeTypeDefaultDescription
actions actions BmxPermissionAction[] | string [] The actions, as objects or JSON: { id, label, description }.
conflict conflict BmxPermissionConflict 'deny-overrides' How disagreeing inherited roles are settled.
defaultEffect default-effect BmxPermissionEffect 'deny' The answer when nothing decides.
hideToolbar hide-toolbar boolean false Leave out the toolbar.
label label string — The matrix's accessible name. Default: "Permissions".
readonly readonly boolean false No editing: the matrix only shows and explains.
resources resources BmxPermissionResource[] | string [] The resources, as objects or JSON: { id, label, description, parent, actions }.
roles roles BmxPermissionRole[] | string [] The roles, as objects or JSON: { id, label, description, inherits }.
strings strings Partial<BmxPermissionMatrixStrings> | string — Words to show instead of the English ones, as an object or JSON.
value value BmxPermissionGrant[] | string [] The saved grants, as objects or JSON: { role, resource, action, effect }. Setting it discards unsaved changes.
visibleRoles visible-roles string[] | string [] The roles shown as columns, by id; all when empty.

Events

EventDetailDescription
bmxPermissionChange BmxPermissionChangeDetail Fired before cells change, from a click, a key, the toolbar or setGrant. Cancel it to refuse.
bmxPermissionSave BmxPermissionSaveDetail Fired when Save is pressed. Cancel it to keep the changes pending (to save them yourself later, call commit()).

Methods

MethodSignatureDescription
collapseAll collapseAll() => Promise<void> Closes every resource that has children.
commit commit() => Promise<void> Makes the current grants the saved state, without an event (after saving them yourself).
discard discard() => Promise<void> Back to the saved state.
expandAll expandAll() => Promise<void> Opens every resource.
explain explain(role: string, resource: string, action: string) => Promise<BmxPermissionExplanation> The effective answer for a cell, and why.
exportCsv exportCsv() => Promise<string> The effective matrix as CSV (roles' actions across, resources down).
getChanges getChanges() => Promise<BmxPermissionChange[]> What differs from the saved state.
getValue getValue() => Promise<BmxPermissionGrant[]> Every explicit grant as it stands, unsaved changes included.
redo redo() => Promise<boolean> Redoes the last undone change. Resolves whether there was one.
save save() => Promise<boolean> Asks to save: fires bmxPermissionSave; unless it is cancelled, the current grants become the saved state.
setGrant setGrant(role: string, resource: string, action: string, effect: BmxPermissionEffect | null) => Promise<boolean> Sets one cell: allow, deny, or null to inherit. Resolves whether it changed.
undo undo() => Promise<boolean> Undoes the last change. Resolves whether there was one.

CSS shadow parts

PartDescription
action-header An action's header.
cell One cell; also cell-allow, cell-deny, cell-explicit, cell-changed, cell-selected.
explain The line saying why the focused cell is what it is.
grid The scrolling matrix.
header The two header rows.
resource A resource's name cell.
review The list of unsaved changes.
role-header A role's header, over its actions.
row One resource's row.
toolbar The row of filter, edit and save controls.

CSS custom properties

PropertyDescription
--bmx-permission-matrix-allow The colour of an allowed cell.
--bmx-permission-matrix-cell-width The width of each action column.
--bmx-permission-matrix-deny The colour of a denied cell.
--bmx-permission-matrix-height The matrix's height; it scrolls inside it.
--bmx-permission-matrix-indent How far each level of the resource tree is indented.
--bmx-permission-matrix-name-width The width of the resource column.
--bmx-permission-matrix-row-height The height of each row.