Use checkbox-tree library
This commit is contained in:
@@ -8,7 +8,7 @@ globalThis.document = dom.window.document;
|
|||||||
globalThis.Element = dom.window.Element;
|
globalThis.Element = dom.window.Element;
|
||||||
globalThis.localStorage = dom.window.localStorage;
|
globalThis.localStorage = dom.window.localStorage;
|
||||||
|
|
||||||
const { CheckboxTree } = await import('../web/lib/checkbox-tree/checkbox-tree.js');
|
const { CheckboxTree } = await import('../web/vendor/checkbox-tree/checkbox-tree.js');
|
||||||
|
|
||||||
function createTree(options = {}) {
|
function createTree(options = {}) {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
@@ -57,6 +57,11 @@ test('partial child selection makes the parent indeterminate', () => {
|
|||||||
assert.deepEqual(tree.getSelectedIds(), ['first']);
|
assert.deepEqual(tree.getSelectedIds(), ['first']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('can initially select every selectable node', () => {
|
||||||
|
const { tree } = createTree({ initiallySelected: true });
|
||||||
|
assert.deepEqual(tree.getSelectedIds(), ['parent', 'first', 'second']);
|
||||||
|
});
|
||||||
|
|
||||||
test('persists selection and collapsed branches per configured key', () => {
|
test('persists selection and collapsed branches per configured key', () => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
const first = createTree({ storageKey: 'example-tree' });
|
const first = createTree({ storageKey: 'example-tree' });
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="icons/favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="icons/favicon.svg">
|
||||||
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
||||||
<link rel="apple-touch-icon-precomposed" href="apple-touch-icon-precomposed.png">
|
<link rel="apple-touch-icon-precomposed" href="apple-touch-icon-precomposed.png">
|
||||||
<link rel="stylesheet" href="lib/checkbox-tree/checkbox-tree.css">
|
<link rel="stylesheet" href="vendor/checkbox-tree/checkbox-tree.css">
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { CheckboxTree } from '../lib/checkbox-tree/checkbox-tree.js';
|
import { CheckboxTree } from '../vendor/checkbox-tree/checkbox-tree.js';
|
||||||
import { getSortedKeys } from './sort.js';
|
import { getSortedKeys } from './sort.js';
|
||||||
|
|
||||||
const STORAGE_KEY = 'tree-state-v2';
|
const STORAGE_KEY = 'tree-state-v2';
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
# CheckboxTree
|
|
||||||
|
|
||||||
A dependency-free, instance-based checkbox tree for browser ES modules.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```html
|
|
||||||
<link rel="stylesheet" href="./checkbox-tree.css">
|
|
||||||
<div id="example-tree"></div>
|
|
||||||
<script type="module">
|
|
||||||
import { CheckboxTree } from './checkbox-tree.js';
|
|
||||||
|
|
||||||
const tree = new CheckboxTree(document.querySelector('#example-tree'), {
|
|
||||||
storageKey: 'example-tree-state',
|
|
||||||
onSelectionChange: ({ selectedIds, selectedNodes }) => {
|
|
||||||
console.log(selectedIds, selectedNodes);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tree.setData([
|
|
||||||
{
|
|
||||||
id: 'fruit',
|
|
||||||
label: 'Fruit',
|
|
||||||
children: [
|
|
||||||
{ id: 'apple', label: 'Apple', metadata: { color: 'red' } },
|
|
||||||
{ id: 'pear', label: 'Pear', metadata: { color: 'green' } }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
Every node requires a unique string `id` and a string `label`. Nodes may also have
|
|
||||||
`children`, application-owned `metadata`, and `selectable` or `disabled` flags.
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
- `new CheckboxTree(container, options)` creates an independent instance.
|
|
||||||
- `setData(nodes)` validates and renders a new node array, then restores saved state.
|
|
||||||
- `getSelectedIds()` returns selected node IDs.
|
|
||||||
- `getSelectedNodes()` returns the selected source node objects.
|
|
||||||
- `setSelectedIds(ids, { notify })` replaces the current selection.
|
|
||||||
- `restoreState()` restores selection and expansion when `storageKey` is configured.
|
|
||||||
- `destroy()` removes listeners, markup, and the root CSS class.
|
|
||||||
|
|
||||||
Options are `initiallyCollapsed` (default `true`), `storageKey` (default `null`),
|
|
||||||
and `onSelectionChange`. Styling is namespaced under `.checkbox-tree` and its
|
|
||||||
custom properties can be overridden by a consuming application.
|
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
# Checkbox Tree runtime snapshot
|
||||||
|
|
||||||
|
Vendored from `@aaronaxvig/checkbox-tree` version `0.1.0` while
|
||||||
|
`git.axvig.com` is unavailable. Do not edit these runtime files directly; update
|
||||||
|
the canonical `checkbox-tree` repository and refresh this snapshot.
|
||||||
+4
@@ -36,6 +36,10 @@
|
|||||||
font: inherit;
|
font: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox-tree__toggle[aria-expanded="true"] {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
.checkbox-tree__toggle:focus-visible {
|
.checkbox-tree__toggle:focus-visible {
|
||||||
outline: 2px solid currentColor;
|
outline: 2px solid currentColor;
|
||||||
outline-offset: 1px;
|
outline-offset: 1px;
|
||||||
+3
-1
@@ -1,5 +1,6 @@
|
|||||||
const DEFAULT_OPTIONS = {
|
const DEFAULT_OPTIONS = {
|
||||||
initiallyCollapsed: true,
|
initiallyCollapsed: true,
|
||||||
|
initiallySelected: false,
|
||||||
storageKey: null,
|
storageKey: null,
|
||||||
onSelectionChange: null
|
onSelectionChange: null
|
||||||
};
|
};
|
||||||
@@ -31,6 +32,7 @@ export class CheckboxTree {
|
|||||||
this.rootNodes = nodes;
|
this.rootNodes = nodes;
|
||||||
this.indexNodes(nodes);
|
this.indexNodes(nodes);
|
||||||
this.render();
|
this.render();
|
||||||
|
this.updateAllParentCheckboxes();
|
||||||
this.restoreState();
|
this.restoreState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +127,7 @@ export class CheckboxTree {
|
|||||||
checkbox.dataset.nodeId = node.id;
|
checkbox.dataset.nodeId = node.id;
|
||||||
checkbox.dataset.selectable = 'true';
|
checkbox.dataset.selectable = 'true';
|
||||||
checkbox.disabled = node.disabled === true;
|
checkbox.disabled = node.disabled === true;
|
||||||
|
checkbox.checked = this.options.initiallySelected && !checkbox.disabled;
|
||||||
label.appendChild(checkbox);
|
label.appendChild(checkbox);
|
||||||
}
|
}
|
||||||
label.appendChild(document.createTextNode(node.label));
|
label.appendChild(document.createTextNode(node.label));
|
||||||
@@ -198,7 +201,6 @@ export class CheckboxTree {
|
|||||||
toggle.setAttribute('aria-expanded', String(expanded));
|
toggle.setAttribute('aria-expanded', String(expanded));
|
||||||
const label = this.nodesById.get(item.dataset.nodeId)?.label ?? 'branch';
|
const label = this.nodesById.get(item.dataset.nodeId)?.label ?? 'branch';
|
||||||
toggle.setAttribute('aria-label', `${expanded ? 'Collapse' : 'Expand'} ${label}`);
|
toggle.setAttribute('aria-label', `${expanded ? 'Collapse' : 'Expand'} ${label}`);
|
||||||
toggle.textContent = expanded ? '▼' : '▶';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
directChildrenContainer(item) {
|
directChildrenContainer(item) {
|
||||||
Reference in New Issue
Block a user