<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
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
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
| Event | Detail | Description |
|---|---|---|
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
| Method | Signature | Description |
|---|---|---|
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
| Part | Description |
|---|---|
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
| Property | Description |
|---|---|
--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. |