tree-element 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -20
- package/lib/dataLoader.d.ts.map +1 -1
- package/lib/dataLoader.js +4 -1
- package/lib/dataLoader.js.map +1 -1
- package/lib/dragAndDropHandler/generateHitAreas.d.ts.map +1 -1
- package/lib/dragAndDropHandler/generateHitAreas.js +3 -2
- package/lib/dragAndDropHandler/generateHitAreas.js.map +1 -1
- package/lib/dragAndDropHandler/index.d.ts +2 -4
- package/lib/dragAndDropHandler/index.d.ts.map +1 -1
- package/lib/dragAndDropHandler/index.js +2 -9
- package/lib/dragAndDropHandler/index.js.map +1 -1
- package/lib/elementsRenderer.d.ts +7 -2
- package/lib/elementsRenderer.d.ts.map +1 -1
- package/lib/elementsRenderer.js +70 -23
- package/lib/elementsRenderer.js.map +1 -1
- package/lib/events.d.ts +13 -7
- package/lib/events.d.ts.map +1 -1
- package/lib/folderOpenState.d.ts +8 -0
- package/lib/folderOpenState.d.ts.map +1 -0
- package/lib/folderOpenState.js +22 -0
- package/lib/folderOpenState.js.map +1 -0
- package/lib/index.d.ts +17 -16
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +93 -49
- package/lib/index.js.map +1 -1
- package/lib/keyHandler.d.ts +2 -0
- package/lib/keyHandler.d.ts.map +1 -1
- package/lib/keyHandler.js +18 -0
- package/lib/keyHandler.js.map +1 -1
- package/lib/mouseHandler.d.ts.map +1 -1
- package/lib/mouseHandler.js +12 -12
- package/lib/mouseHandler.js.map +1 -1
- package/lib/node.d.ts +12 -10
- package/lib/node.d.ts.map +1 -1
- package/lib/node.js +12 -10
- package/lib/node.js.map +1 -1
- package/lib/nodeElement/folderElement.d.ts +7 -5
- package/lib/nodeElement/folderElement.d.ts.map +1 -1
- package/lib/nodeElement/folderElement.js +33 -53
- package/lib/nodeElement/folderElement.js.map +1 -1
- package/lib/options.d.ts +19 -17
- package/lib/options.d.ts.map +1 -1
- package/lib/options.js +12 -0
- package/lib/options.js.map +1 -0
- package/lib/saveStateHandler.d.ts.map +1 -1
- package/lib/saveStateHandler.js +6 -6
- package/lib/saveStateHandler.js.map +1 -1
- package/lib/selectNodeHandler.d.ts.map +1 -1
- package/lib/selectNodeHandler.js +13 -1
- package/lib/selectNodeHandler.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/package.json +35 -29
- package/src/dataLoader.ts +3 -1
- package/src/dragAndDropHandler/generateHitAreas.ts +3 -2
- package/src/dragAndDropHandler/index.ts +3 -17
- package/src/elementsRenderer.ts +84 -33
- package/src/events.ts +13 -7
- package/src/folderOpenState.ts +38 -0
- package/src/index.ts +109 -59
- package/src/keyHandler.ts +20 -0
- package/src/mouseHandler.ts +15 -13
- package/src/node.ts +12 -10
- package/src/nodeElement/folderElement.ts +45 -70
- package/src/options.ts +36 -17
- package/src/saveStateHandler.ts +5 -8
- package/src/selectNodeHandler.ts +12 -1
- package/src/version.ts +1 -1
- package/tree_element.css +32 -5
- package/tree_element.debug.js +291 -165
- package/tree_element.debug.js.map +1 -1
- package/tree_element.js +2 -2
- package/tree_element.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tree-element",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"packageManager": "pnpm@12.16.1",
|
|
4
5
|
"description": "Tree widget in plain javascript",
|
|
5
6
|
"keywords": [
|
|
6
7
|
"tree"
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"vitest": "pnpm build-debug && vitest run --coverage --config config/vitest.config.mjs",
|
|
44
45
|
"vitest-watch": "vitest --config config/vitest.config.mjs",
|
|
45
46
|
"lint": "eslint css src playwright test",
|
|
47
|
+
"lint-docs": "pnpm docs-api && vale --glob='!docs/.vitepress/**' README.md CHANGELOG.md docs",
|
|
46
48
|
"production": "./config/production",
|
|
47
49
|
"devserver": "SERVE=true rollup --config config/rollup.config.mjs --watch",
|
|
48
50
|
"docs-api": "typedoc && node docs/scripts/fixApiLinks.mjs",
|
|
@@ -58,66 +60,70 @@
|
|
|
58
60
|
"release": "node config/release.mjs",
|
|
59
61
|
"playwright": "pnpm build-with-coverage && playwright test --config config/playwright.config.js",
|
|
60
62
|
"test": "pnpm vitest && pnpm playwright",
|
|
61
|
-
"docs-check": "kiira check"
|
|
63
|
+
"docs-check": "kiira check",
|
|
64
|
+
"mutation": "stryker run config/stryker.config.mjs"
|
|
62
65
|
},
|
|
63
66
|
"devDependencies": {
|
|
64
67
|
"@axe-core/playwright": "^4.14.0",
|
|
65
|
-
"@babel/cli": "^8.0
|
|
66
|
-
"@babel/core": "^8.
|
|
68
|
+
"@babel/cli": "^8.1.0",
|
|
69
|
+
"@babel/core": "^8.1.2",
|
|
67
70
|
"@babel/preset-env": "^8.0.7",
|
|
68
71
|
"@babel/preset-typescript": "^8.0.7",
|
|
69
|
-
"@cspell/eslint-plugin": "^10.4.
|
|
72
|
+
"@cspell/eslint-plugin": "^10.4.1",
|
|
70
73
|
"@eslint/css": "^2.0.1",
|
|
71
74
|
"@eslint/js": "^10.0.1",
|
|
72
|
-
"@playwright/test": "^1.
|
|
75
|
+
"@playwright/test": "^1.65.0",
|
|
73
76
|
"@rollup/plugin-babel": "^7.1.0",
|
|
74
77
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
75
|
-
"@rollup/plugin-terser": "^1.0.
|
|
78
|
+
"@rollup/plugin-terser": "^1.0.2",
|
|
79
|
+
"@stryker-mutator/core": "^10.1.1",
|
|
80
|
+
"@stryker-mutator/typescript-checker": "^10.1.1",
|
|
81
|
+
"@stryker-mutator/vitest-runner": "^10.1.1",
|
|
76
82
|
"@testing-library/dom": "^10.4.2",
|
|
77
83
|
"@testing-library/jest-dom": "^7.0.1",
|
|
78
|
-
"@testing-library/user-event": "^14.6.
|
|
84
|
+
"@testing-library/user-event": "^14.6.10",
|
|
79
85
|
"@types/debug": "^4.1.13",
|
|
80
|
-
"@types/node": "^26.6.
|
|
81
|
-
"@vitest/coverage-istanbul": "^5.0
|
|
82
|
-
"@vitest/eslint-plugin": "^1.6.
|
|
86
|
+
"@types/node": "^26.6.6",
|
|
87
|
+
"@vitest/coverage-istanbul": "^5.1.0",
|
|
88
|
+
"@vitest/eslint-plugin": "^1.6.28",
|
|
83
89
|
"axe-core": "^4.14.0",
|
|
84
90
|
"babel-plugin-istanbul": "^8.0.0",
|
|
85
91
|
"babel-plugin-prefix-private-members": "^0.3.1",
|
|
86
|
-
"eslint": "^10.
|
|
92
|
+
"eslint": "^10.14.0",
|
|
87
93
|
"eslint-import-resolver-typescript": "^4.4.5",
|
|
88
94
|
"eslint-plugin-compat": "^7.0.2",
|
|
89
|
-
"eslint-plugin-import-x": "^4.
|
|
90
|
-
"eslint-plugin-jest-dom": "^5.10.
|
|
95
|
+
"eslint-plugin-import-x": "^4.18.1",
|
|
96
|
+
"eslint-plugin-jest-dom": "^5.10.2",
|
|
91
97
|
"eslint-plugin-jest-extended": "github:mbraak/eslint-plugin-jest-extended#modernize",
|
|
92
98
|
"eslint-plugin-no-unsanitized": "^4.1.5",
|
|
93
|
-
"eslint-plugin-perfectionist": "^5.
|
|
94
|
-
"eslint-plugin-playwright": "^2.12.
|
|
99
|
+
"eslint-plugin-perfectionist": "^5.13.0",
|
|
100
|
+
"eslint-plugin-playwright": "^2.12.2",
|
|
95
101
|
"eslint-plugin-testing-library": "^7.16.2",
|
|
96
102
|
"eslint-plugin-tsdoc": "^0.5.4",
|
|
97
|
-
"eslint-plugin-unicorn": "^
|
|
103
|
+
"eslint-plugin-unicorn": "^79.0.0",
|
|
98
104
|
"jest-extended": "^7.0.0",
|
|
99
|
-
"jsdom": "^
|
|
105
|
+
"jsdom": "^31.0.1",
|
|
100
106
|
"jsdom-testing-mocks": "^1.16.0",
|
|
101
107
|
"jsonfile": "^6.2.1",
|
|
102
|
-
"kiira": "^0.7.
|
|
103
|
-
"kiira-core": "^0.7.
|
|
108
|
+
"kiira": "^0.7.1",
|
|
109
|
+
"kiira-core": "^0.7.1",
|
|
104
110
|
"lodash": "^4.18.1",
|
|
105
|
-
"msw": "^3.0.
|
|
106
|
-
"postcss": "^8.5.
|
|
111
|
+
"msw": "^3.0.5",
|
|
112
|
+
"postcss": "^8.5.32",
|
|
107
113
|
"postcss-cli": "^12.0.0",
|
|
108
114
|
"postcss-import": "^17.0.1",
|
|
109
115
|
"postcss-load-config": "^6.0.1",
|
|
110
|
-
"postcss-nested": "^9.0.
|
|
111
|
-
"prettier": "^3.
|
|
112
|
-
"rollup": "^4.
|
|
116
|
+
"postcss-nested": "^9.0.1",
|
|
117
|
+
"prettier": "^3.10.1",
|
|
118
|
+
"rollup": "^4.68.2",
|
|
113
119
|
"rollup-plugin-serve": "^3.0.0",
|
|
114
120
|
"rollup-plugin-tsconfig-paths": "^1.5.2",
|
|
115
|
-
"tslib": "^2.
|
|
121
|
+
"tslib": "^2.9.1",
|
|
116
122
|
"typedoc": "^0.28.20",
|
|
117
|
-
"typedoc-plugin-markdown": "^4.14.
|
|
123
|
+
"typedoc-plugin-markdown": "^4.14.1",
|
|
118
124
|
"typescript": "^6.0.3",
|
|
119
|
-
"typescript-eslint": "^8.
|
|
125
|
+
"typescript-eslint": "^8.72.2",
|
|
120
126
|
"vitepress": "^1.6.4",
|
|
121
|
-
"vitest": "^5.0
|
|
127
|
+
"vitest": "^5.1.0"
|
|
122
128
|
}
|
|
123
129
|
}
|
package/src/dataLoader.ts
CHANGED
|
@@ -80,8 +80,10 @@ export default class DataLoader {
|
|
|
80
80
|
const signal = this.abortController.signal;
|
|
81
81
|
url.setSearchParam("_", Date.now().toString());
|
|
82
82
|
|
|
83
|
+
// Accept, not Content-Type: the request has no body. A cross-origin request
|
|
84
|
+
// with only an Accept header also needs no CORS preflight.
|
|
83
85
|
return fetch(url.value(), {
|
|
84
|
-
headers: {
|
|
86
|
+
headers: { Accept: "application/json" },
|
|
85
87
|
signal,
|
|
86
88
|
})
|
|
87
89
|
.then(handleResponse)
|
|
@@ -31,8 +31,9 @@ export const generateHitPositions = (
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
const handleAfterOpenFolder = (node: Node, nextNode: Node | null) => {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// Not called for the current node: handleOpenFolder stops iterating there
|
|
35
|
+
if (nextNode === currentNode) {
|
|
36
|
+
// Dropping the current node after this folder would leave it where it is
|
|
36
37
|
addHitPosition(node, null, lastTop);
|
|
37
38
|
} else {
|
|
38
39
|
addHitPosition(node, "after", lastTop);
|
|
@@ -44,7 +44,6 @@ interface DragAndDropHandlerParams {
|
|
|
44
44
|
openFolderDelay: false | number;
|
|
45
45
|
openNode: OpenNode;
|
|
46
46
|
refreshElements: RefreshElements;
|
|
47
|
-
slide: boolean;
|
|
48
47
|
treeElement: HTMLElement;
|
|
49
48
|
triggerEvent: TriggerEvent;
|
|
50
49
|
}
|
|
@@ -75,7 +74,6 @@ export class DragAndDropHandler {
|
|
|
75
74
|
private openNode: OpenNode;
|
|
76
75
|
private previousGhost: DropHint | null;
|
|
77
76
|
private refreshElements: RefreshElements;
|
|
78
|
-
private slide: boolean;
|
|
79
77
|
private treeElement: HTMLElement;
|
|
80
78
|
private triggerEvent: TriggerEvent;
|
|
81
79
|
|
|
@@ -94,7 +92,6 @@ export class DragAndDropHandler {
|
|
|
94
92
|
openFolderDelay,
|
|
95
93
|
openNode,
|
|
96
94
|
refreshElements,
|
|
97
|
-
slide,
|
|
98
95
|
treeElement,
|
|
99
96
|
triggerEvent,
|
|
100
97
|
}: DragAndDropHandlerParams) {
|
|
@@ -112,7 +109,6 @@ export class DragAndDropHandler {
|
|
|
112
109
|
this.openFolderDelay = openFolderDelay;
|
|
113
110
|
this.openNode = openNode;
|
|
114
111
|
this.refreshElements = refreshElements;
|
|
115
|
-
this.slide = slide;
|
|
116
112
|
this.treeElement = treeElement;
|
|
117
113
|
this.triggerEvent = triggerEvent;
|
|
118
114
|
|
|
@@ -157,10 +153,6 @@ export class DragAndDropHandler {
|
|
|
157
153
|
const area = this.findHoveredArea(positionInfo.pageX, positionInfo.pageY);
|
|
158
154
|
|
|
159
155
|
if (area && this.canMoveToArea(area, this.currentItem)) {
|
|
160
|
-
if (!area.node.isFolder()) {
|
|
161
|
-
this.stopOpenFolderTimer();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
156
|
if (this.hoveredArea !== area) {
|
|
165
157
|
this.hoveredArea = area;
|
|
166
158
|
|
|
@@ -212,7 +204,7 @@ export class DragAndDropHandler {
|
|
|
212
204
|
return true;
|
|
213
205
|
}
|
|
214
206
|
|
|
215
|
-
public mouseStop(positionInfo: PositionInfo):
|
|
207
|
+
public mouseStop(positionInfo: PositionInfo): void {
|
|
216
208
|
this.moveItem(positionInfo);
|
|
217
209
|
this.clear();
|
|
218
210
|
this.removeHover();
|
|
@@ -231,8 +223,6 @@ export class DragAndDropHandler {
|
|
|
231
223
|
if (!this.hoveredArea && currentItem && this.onDragStop) {
|
|
232
224
|
this.onDragStop(currentItem.node, positionInfo.originalEvent);
|
|
233
225
|
}
|
|
234
|
-
|
|
235
|
-
return false;
|
|
236
226
|
}
|
|
237
227
|
|
|
238
228
|
public refresh(): void {
|
|
@@ -360,11 +350,7 @@ export class DragAndDropHandler {
|
|
|
360
350
|
}
|
|
361
351
|
|
|
362
352
|
private mustCaptureElement(element: HTMLElement): boolean {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return (
|
|
366
|
-
nodeName !== "INPUT" && nodeName !== "SELECT" && nodeName !== "TEXTAREA"
|
|
367
|
-
);
|
|
353
|
+
return !["INPUT", "SELECT", "TEXTAREA"].includes(element.nodeName);
|
|
368
354
|
}
|
|
369
355
|
|
|
370
356
|
private mustOpenFolderTimer(area: HitArea): boolean {
|
|
@@ -389,7 +375,7 @@ export class DragAndDropHandler {
|
|
|
389
375
|
|
|
390
376
|
private startOpenFolderTimer(folder: Node): void {
|
|
391
377
|
const openFolder = (): void => {
|
|
392
|
-
void this.openNode(folder
|
|
378
|
+
void this.openNode(folder).then(() => {
|
|
393
379
|
this.refresh();
|
|
394
380
|
this.updateDropHint();
|
|
395
381
|
});
|
package/src/elementsRenderer.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { ClassNames } from "./classNames";
|
|
2
|
+
import type { FolderIcons } from "./folderOpenState";
|
|
2
3
|
import type { GetTree, IsNodeSelected } from "./methodTypes";
|
|
3
4
|
import type { Node } from "./node";
|
|
4
5
|
import type { IconElement, OnCreateLi } from "./options";
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
+
import { setFolderLiOpenState, setTogglerOpenState } from "./folderOpenState";
|
|
7
8
|
|
|
8
9
|
interface ElementsRendererParams {
|
|
9
10
|
ariaLabel?: string;
|
|
@@ -37,8 +38,7 @@ interface ElementsRendererParams {
|
|
|
37
38
|
* selected and loading classes.
|
|
38
39
|
*/
|
|
39
40
|
export default class ElementsRenderer {
|
|
40
|
-
public
|
|
41
|
-
public openedIconElement?: globalThis.Node;
|
|
41
|
+
public icons: FolderIcons;
|
|
42
42
|
private ariaLabel?: string;
|
|
43
43
|
private ariaLabelledBy?: string;
|
|
44
44
|
private autoEscape: boolean;
|
|
@@ -94,14 +94,17 @@ export default class ElementsRenderer {
|
|
|
94
94
|
this.setNodeElement = setNodeElement;
|
|
95
95
|
this.showEmptyFolder = showEmptyFolder;
|
|
96
96
|
this.tabIndex = tabIndex;
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this.
|
|
104
|
-
this.
|
|
97
|
+
this.icons = {
|
|
98
|
+
closed: this.createButtonElement(closedIcon),
|
|
99
|
+
opened: this.createButtonElement(openedIcon),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const templates = this.createTemplates();
|
|
103
|
+
this.closedFolderTemplate = templates.closedFolderTemplate;
|
|
104
|
+
this.groupUlTemplate = templates.groupUlTemplate;
|
|
105
|
+
this.nodeTemplate = templates.nodeTemplate;
|
|
106
|
+
this.openedFolderTemplate = templates.openedFolderTemplate;
|
|
107
|
+
this.rootUlTemplate = templates.rootUlTemplate;
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
public render(fromNode: Node | null): void {
|
|
@@ -150,6 +153,50 @@ export default class ElementsRenderer {
|
|
|
150
153
|
}
|
|
151
154
|
}
|
|
152
155
|
|
|
156
|
+
/* Change dragAndDrop after construction. The rendered lists and the
|
|
157
|
+
* templates for new lists get or lose the dnd class.
|
|
158
|
+
*/
|
|
159
|
+
public setDragAndDrop(dragAndDrop: boolean): void {
|
|
160
|
+
this.dragAndDrop = dragAndDrop;
|
|
161
|
+
this.updateTemplates();
|
|
162
|
+
|
|
163
|
+
for (const ul of this.htmlElement.querySelectorAll(
|
|
164
|
+
`ul.${this.classNames.common}`,
|
|
165
|
+
)) {
|
|
166
|
+
ul.classList.toggle(this.classNames.dnd, dragAndDrop);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Change multiSelect after construction. Only the templates are updated;
|
|
171
|
+
* the tree must be rendered again.
|
|
172
|
+
*/
|
|
173
|
+
public setMultiSelect(multiSelect: boolean): void {
|
|
174
|
+
this.multiSelect = multiSelect;
|
|
175
|
+
this.updateTemplates();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* Change tabIndex after construction. The rendered nodes and the
|
|
179
|
+
* templates for new nodes get the new tabindex. The tab stop is updated by
|
|
180
|
+
* TreeElement.updateTabStop.
|
|
181
|
+
*/
|
|
182
|
+
public setTabIndex(tabIndex: number | undefined): void {
|
|
183
|
+
this.tabIndex = tabIndex;
|
|
184
|
+
this.updateTemplates();
|
|
185
|
+
|
|
186
|
+
for (const li of this.htmlElement.querySelectorAll("li[role=treeitem]")) {
|
|
187
|
+
if (tabIndex === undefined) {
|
|
188
|
+
li.removeAttribute("tabindex");
|
|
189
|
+
} else if (
|
|
190
|
+
!this.multiSelect &&
|
|
191
|
+
li.getAttribute("aria-selected") === "true"
|
|
192
|
+
) {
|
|
193
|
+
li.setAttribute("tabindex", String(tabIndex));
|
|
194
|
+
} else {
|
|
195
|
+
li.setAttribute("tabindex", "-1");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
153
200
|
private attachNodeData(node: Node, li: HTMLElement): void {
|
|
154
201
|
node.element = li;
|
|
155
202
|
this.setNodeElement(li, node);
|
|
@@ -228,35 +275,24 @@ export default class ElementsRenderer {
|
|
|
228
275
|
* state, so there is a template for each.
|
|
229
276
|
*/
|
|
230
277
|
private createFolderTemplate(isOpen: boolean): HTMLLIElement {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
278
|
+
const li = this.createLiTemplate(
|
|
279
|
+
`${this.classNames.common} ${this.classNames.folder}`,
|
|
280
|
+
);
|
|
281
|
+
setFolderLiOpenState(li, isOpen, this.classNames);
|
|
236
282
|
|
|
237
|
-
const li = this.createLiTemplate(liClasses.join(" "));
|
|
238
283
|
const div = li.firstChild as HTMLDivElement;
|
|
239
284
|
|
|
240
285
|
// The toggler is hidden from screen readers: the icon is decoration, and
|
|
241
286
|
// aria-expanded on the treeitem has the open state.
|
|
242
287
|
const toggler = document.createElement("span");
|
|
243
|
-
toggler.className = this.getButtonClasses(
|
|
288
|
+
toggler.className = this.getButtonClasses();
|
|
244
289
|
toggler.setAttribute("aria-hidden", "true");
|
|
245
|
-
|
|
246
|
-
const iconElement = isOpen
|
|
247
|
-
? this.openedIconElement
|
|
248
|
-
: this.closedIconElement;
|
|
249
|
-
|
|
250
|
-
if (iconElement) {
|
|
251
|
-
toggler.append(iconElement.cloneNode(true));
|
|
252
|
-
}
|
|
290
|
+
setTogglerOpenState(toggler, isOpen, this.classNames, this.icons);
|
|
253
291
|
|
|
254
292
|
if (this.buttonLeft) {
|
|
255
293
|
div.append(toggler);
|
|
256
294
|
}
|
|
257
295
|
|
|
258
|
-
li.setAttribute("aria-expanded", getBoolString(isOpen));
|
|
259
|
-
|
|
260
296
|
// title span
|
|
261
297
|
div.append(this.createTitleSpanTemplate(true));
|
|
262
298
|
|
|
@@ -350,6 +386,16 @@ export default class ElementsRenderer {
|
|
|
350
386
|
return li;
|
|
351
387
|
}
|
|
352
388
|
|
|
389
|
+
private createTemplates() {
|
|
390
|
+
return {
|
|
391
|
+
closedFolderTemplate: this.createFolderTemplate(false),
|
|
392
|
+
groupUlTemplate: this.createUlTemplate(false),
|
|
393
|
+
nodeTemplate: this.createNodeTemplate(),
|
|
394
|
+
openedFolderTemplate: this.createFolderTemplate(true),
|
|
395
|
+
rootUlTemplate: this.createUlTemplate(true),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
353
399
|
private createTitleSpanTemplate(isFolder: boolean): HTMLSpanElement {
|
|
354
400
|
const titleSpan = document.createElement("span");
|
|
355
401
|
|
|
@@ -443,13 +489,9 @@ export default class ElementsRenderer {
|
|
|
443
489
|
li.setAttribute("aria-label", titleSpan.textContent);
|
|
444
490
|
}
|
|
445
491
|
|
|
446
|
-
private getButtonClasses(
|
|
492
|
+
private getButtonClasses(): string {
|
|
447
493
|
const classes = [this.classNames.toggler, this.classNames.common];
|
|
448
494
|
|
|
449
|
-
if (!isOpen) {
|
|
450
|
-
classes.push(this.classNames.closed);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
495
|
if (this.buttonLeft) {
|
|
454
496
|
classes.push(this.classNames.togglerLeft);
|
|
455
497
|
} else {
|
|
@@ -465,4 +507,13 @@ export default class ElementsRenderer {
|
|
|
465
507
|
private mustRenderChildren(node: Node): boolean {
|
|
466
508
|
return node.hasChildren() && node.is_open === true;
|
|
467
509
|
}
|
|
510
|
+
|
|
511
|
+
private updateTemplates(): void {
|
|
512
|
+
const templates = this.createTemplates();
|
|
513
|
+
this.closedFolderTemplate = templates.closedFolderTemplate;
|
|
514
|
+
this.groupUlTemplate = templates.groupUlTemplate;
|
|
515
|
+
this.nodeTemplate = templates.nodeTemplate;
|
|
516
|
+
this.openedFolderTemplate = templates.openedFolderTemplate;
|
|
517
|
+
this.rootUlTemplate = templates.rootUlTemplate;
|
|
518
|
+
}
|
|
468
519
|
}
|
package/src/events.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface TreeEvents {
|
|
|
25
25
|
/**
|
|
26
26
|
* Dispatched once, after the tree is rendered for the first time. With
|
|
27
27
|
* `dataUrl` this is after the data has arrived, so it is the moment the
|
|
28
|
-
*
|
|
28
|
+
* API is safe to use:
|
|
29
29
|
*
|
|
30
30
|
* ```js
|
|
31
31
|
* element.addEventListener("tree.init", () => {
|
|
@@ -85,7 +85,7 @@ export interface TreeEvents {
|
|
|
85
85
|
originalEvent: MouseEvent;
|
|
86
86
|
};
|
|
87
87
|
/**
|
|
88
|
-
* Dispatched
|
|
88
|
+
* Dispatched when a node is right-clicked, unless `useContextMenu` is
|
|
89
89
|
* `false`. The browser's own menu is suppressed.
|
|
90
90
|
*
|
|
91
91
|
* @group Mouse
|
|
@@ -96,7 +96,7 @@ export interface TreeEvents {
|
|
|
96
96
|
};
|
|
97
97
|
/**
|
|
98
98
|
* Dispatched when `Enter` is pressed on the selected node, or on the
|
|
99
|
-
* focused node when no node is selected. Use it for what
|
|
99
|
+
* focused node when no node is selected. Use it for what clicking a
|
|
100
100
|
* link would do, like opening the edit page of the node:
|
|
101
101
|
*
|
|
102
102
|
* ```js
|
|
@@ -142,7 +142,9 @@ export interface TreeEvents {
|
|
|
142
142
|
*/
|
|
143
143
|
"tree.select": {
|
|
144
144
|
/**
|
|
145
|
-
The node that was selected before, if any.
|
|
145
|
+
The node that was selected before, if any. When several nodes were
|
|
146
|
+
selected, this is the first, and the others get a `tree.deselect`,
|
|
147
|
+
dispatched before this event.
|
|
146
148
|
*/
|
|
147
149
|
deselectedNode: Node | null;
|
|
148
150
|
/**
|
|
@@ -155,13 +157,17 @@ export interface TreeEvents {
|
|
|
155
157
|
* is clicked again or passed to `selectNode` again with
|
|
156
158
|
* `mustToggle: true`. No `tree.select` is dispatched for it.
|
|
157
159
|
*
|
|
160
|
+
* When several nodes are selected and a click or `selectNode` selects just
|
|
161
|
+
* one node, it is dispatched for each node that loses the selection, except
|
|
162
|
+
* the one in the `deselectedNode` of `tree.select`.
|
|
163
|
+
*
|
|
158
164
|
* ```js
|
|
159
165
|
* element.addEventListener("tree.deselect", (e) => {
|
|
160
166
|
* console.log("deselected", e.detail.node.name);
|
|
161
167
|
* });
|
|
162
168
|
* ```
|
|
163
169
|
*
|
|
164
|
-
* Selecting another node does not dispatch it
|
|
170
|
+
* Selecting another node does not dispatch it—that is a `tree.select`
|
|
165
171
|
* with the previous node in `deselectedNode`. It is also not dispatched
|
|
166
172
|
* by `selectNode` without `mustToggle: true`, `selectNode(null)`,
|
|
167
173
|
* `removeFromSelection`, `removeNode`, `loadData` and restoring a saved
|
|
@@ -198,7 +204,7 @@ export interface TreeEvents {
|
|
|
198
204
|
};
|
|
199
205
|
/**
|
|
200
206
|
* Dispatched when data is set on the tree, both from the `data` option
|
|
201
|
-
* and from a
|
|
207
|
+
* and from a URL.
|
|
202
208
|
*
|
|
203
209
|
* @group Loading data
|
|
204
210
|
*/
|
|
@@ -223,7 +229,7 @@ export interface TreeEvents {
|
|
|
223
229
|
};
|
|
224
230
|
/**
|
|
225
231
|
* Dispatched when a request finishes. It is dispatched when the request
|
|
226
|
-
* fails too
|
|
232
|
+
* fails too, and a [`tree.load_failed`](#tree-load-failed) follows it.
|
|
227
233
|
*
|
|
228
234
|
* @group Loading data
|
|
229
235
|
*/
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ClassNames } from "./classNames";
|
|
2
|
+
|
|
3
|
+
import { getBoolString } from "./util";
|
|
4
|
+
|
|
5
|
+
export interface FolderIcons {
|
|
6
|
+
closed?: globalThis.Node;
|
|
7
|
+
opened?: globalThis.Node;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* The open state of a folder is shown on the toggler and on the <li>.
|
|
11
|
+
* These are used both when a folder is rendered and when it is opened or
|
|
12
|
+
* closed, so a rendered folder looks the same as a toggled one.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export const setTogglerOpenState = (
|
|
16
|
+
toggler: HTMLElement,
|
|
17
|
+
isOpen: boolean,
|
|
18
|
+
classNames: ClassNames,
|
|
19
|
+
icons: FolderIcons,
|
|
20
|
+
): void => {
|
|
21
|
+
toggler.classList.toggle(classNames.closed, !isOpen);
|
|
22
|
+
toggler.replaceChildren();
|
|
23
|
+
|
|
24
|
+
const iconElement = isOpen ? icons.opened : icons.closed;
|
|
25
|
+
|
|
26
|
+
if (iconElement) {
|
|
27
|
+
toggler.append(iconElement.cloneNode(true));
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const setFolderLiOpenState = (
|
|
32
|
+
li: HTMLElement,
|
|
33
|
+
isOpen: boolean,
|
|
34
|
+
classNames: ClassNames,
|
|
35
|
+
): void => {
|
|
36
|
+
li.classList.toggle(classNames.closed, !isOpen);
|
|
37
|
+
li.setAttribute("aria-expanded", getBoolString(isOpen));
|
|
38
|
+
};
|