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/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ClassNames } from "./classNames";
|
|
|
2
2
|
import type { TreeEventName, TreeEvents } from "./events";
|
|
3
3
|
import type { PositionInfo } from "./mouseUtils";
|
|
4
4
|
import type { NodeData, NodeId, Position } from "./node";
|
|
5
|
-
import type { TreeElementOptions } from "./options";
|
|
5
|
+
import type { ChangeableOption, TreeElementOptions } from "./options";
|
|
6
6
|
import type { SavedState } from "./saveStateHandler";
|
|
7
7
|
import type { SelectNodeOptions } from "./selectNodeHandler";
|
|
8
8
|
|
|
@@ -15,6 +15,7 @@ import MouseHandler from "./mouseHandler";
|
|
|
15
15
|
import { Node } from "./node";
|
|
16
16
|
import NodeElement from "./nodeElement";
|
|
17
17
|
import FolderElement from "./nodeElement/folderElement";
|
|
18
|
+
import { changeableOptions } from "./options";
|
|
18
19
|
import { getOffsetTop } from "./positionUtils";
|
|
19
20
|
import RequestUrl from "./requestUrl";
|
|
20
21
|
import SaveStateHandler from "./saveStateHandler";
|
|
@@ -109,7 +110,6 @@ export default class TreeElement {
|
|
|
109
110
|
rtl,
|
|
110
111
|
saveState: saveStateOption,
|
|
111
112
|
showEmptyFolder,
|
|
112
|
-
slide,
|
|
113
113
|
tabIndex,
|
|
114
114
|
} = this.options;
|
|
115
115
|
|
|
@@ -215,7 +215,6 @@ export default class TreeElement {
|
|
|
215
215
|
openFolderDelay,
|
|
216
216
|
openNode,
|
|
217
217
|
refreshElements,
|
|
218
|
-
slide,
|
|
219
218
|
treeElement,
|
|
220
219
|
triggerEvent,
|
|
221
220
|
});
|
|
@@ -325,8 +324,9 @@ export default class TreeElement {
|
|
|
325
324
|
}
|
|
326
325
|
|
|
327
326
|
/**
|
|
328
|
-
* Inserts a new node between a node and its parent
|
|
329
|
-
*
|
|
327
|
+
* Inserts a new node between a node and its parent. The siblings of the node
|
|
328
|
+
* move along: the new node takes all children of the parent, and becomes its
|
|
329
|
+
* only child.
|
|
330
330
|
*
|
|
331
331
|
* @returns The new node, or `null` when the node has no parent.
|
|
332
332
|
* @group Changing the tree
|
|
@@ -512,7 +512,7 @@ export default class TreeElement {
|
|
|
512
512
|
}
|
|
513
513
|
|
|
514
514
|
/**
|
|
515
|
-
* Returns the current state
|
|
515
|
+
* Returns the current state—`open_nodes` and `selected_node`—whether or
|
|
516
516
|
* not `saveState` is enabled.
|
|
517
517
|
*
|
|
518
518
|
* @group State
|
|
@@ -531,7 +531,7 @@ export default class TreeElement {
|
|
|
531
531
|
}
|
|
532
532
|
|
|
533
533
|
/**
|
|
534
|
-
* Returns the root node. It is not rendered
|
|
534
|
+
* Returns the root node. It is not rendered, and its `children` are the
|
|
535
535
|
* top-level nodes. Also available as the `tree` property.
|
|
536
536
|
*
|
|
537
537
|
* @group Finding nodes
|
|
@@ -647,7 +647,7 @@ export default class TreeElement {
|
|
|
647
647
|
}
|
|
648
648
|
|
|
649
649
|
/**
|
|
650
|
-
* Opens a folder, and the folders
|
|
650
|
+
* Opens a folder, and the folders that contain it. A node marked `load_on_demand`
|
|
651
651
|
* is fetched first, so await the promise when you need to know it is really
|
|
652
652
|
* open.
|
|
653
653
|
*
|
|
@@ -667,7 +667,7 @@ export default class TreeElement {
|
|
|
667
667
|
openedNode: Node,
|
|
668
668
|
slideOption: boolean,
|
|
669
669
|
): Promise<void> => {
|
|
670
|
-
if (!
|
|
670
|
+
if (!openedNode.children.length) {
|
|
671
671
|
return;
|
|
672
672
|
}
|
|
673
673
|
|
|
@@ -797,13 +797,47 @@ export default class TreeElement {
|
|
|
797
797
|
}
|
|
798
798
|
|
|
799
799
|
/**
|
|
800
|
-
* Changes an option after construction.
|
|
801
|
-
*
|
|
800
|
+
* Changes an option after construction. Only some options can be changed:
|
|
801
|
+
* the others throw an error. See
|
|
802
|
+
* [Options](/reference/options#changing-an-option-later).
|
|
802
803
|
*
|
|
803
804
|
* @group Other
|
|
804
805
|
*/
|
|
805
|
-
public setOption(option:
|
|
806
|
+
public setOption(option: ChangeableOption, value: unknown) {
|
|
807
|
+
if (!(changeableOptions as readonly string[]).includes(option)) {
|
|
808
|
+
throw new Error(
|
|
809
|
+
`The option ${option} can only be set in the constructor`,
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
|
|
806
813
|
(this.options as unknown as Record<string, unknown>)[option] = value;
|
|
814
|
+
|
|
815
|
+
switch (option) {
|
|
816
|
+
case "dragAndDrop": {
|
|
817
|
+
this.renderer.setDragAndDrop(Boolean(value));
|
|
818
|
+
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
case "keyboardSupport": {
|
|
822
|
+
this.keyHandler.setKeyboardSupport(Boolean(value));
|
|
823
|
+
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
case "multiSelect": {
|
|
827
|
+
this.setMultiSelect(Boolean(value));
|
|
828
|
+
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
case "tabIndex": {
|
|
832
|
+
this.options.tabIndex = (value as null | number) ?? undefined;
|
|
833
|
+
this.renderer.setTabIndex(this.options.tabIndex);
|
|
834
|
+
this.fallbackTabStop = null;
|
|
835
|
+
this.updateTabStop();
|
|
836
|
+
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
// No default
|
|
840
|
+
}
|
|
807
841
|
}
|
|
808
842
|
|
|
809
843
|
/**
|
|
@@ -837,8 +871,8 @@ export default class TreeElement {
|
|
|
837
871
|
}
|
|
838
872
|
|
|
839
873
|
/**
|
|
840
|
-
* Returns the tree as a
|
|
841
|
-
*
|
|
874
|
+
* Returns the tree as a JSON string, including changes made through the
|
|
875
|
+
* API.
|
|
842
876
|
*
|
|
843
877
|
* @group Other
|
|
844
878
|
*/
|
|
@@ -851,7 +885,7 @@ export default class TreeElement {
|
|
|
851
885
|
* name.
|
|
852
886
|
*
|
|
853
887
|
* Changing the `id` re-indexes the node. `children` and `parent` are
|
|
854
|
-
* ignored
|
|
888
|
+
* ignored—use `loadData` or `moveNode` for those.
|
|
855
889
|
*
|
|
856
890
|
* @example
|
|
857
891
|
* ```js
|
|
@@ -920,11 +954,9 @@ export default class TreeElement {
|
|
|
920
954
|
|
|
921
955
|
private createFolderElement(node: Node) {
|
|
922
956
|
const classNames = this.classNames;
|
|
923
|
-
const closedIconElement = this.renderer.closedIconElement;
|
|
924
957
|
const getScrollLeft = this.scrollHandler.getScrollLeft.bind(
|
|
925
958
|
this.scrollHandler,
|
|
926
959
|
);
|
|
927
|
-
const openedIconElement = this.renderer.openedIconElement;
|
|
928
960
|
const tabIndex = this.options.tabIndex;
|
|
929
961
|
const treeElement = this.htmlElement;
|
|
930
962
|
const triggerEvent = this.triggerEvent.bind(this);
|
|
@@ -940,11 +972,10 @@ export default class TreeElement {
|
|
|
940
972
|
|
|
941
973
|
return new FolderElement({
|
|
942
974
|
classNames,
|
|
943
|
-
closedIconElement,
|
|
944
975
|
getScrollLeft,
|
|
976
|
+
icons: this.renderer.icons,
|
|
945
977
|
multiSelect: this.options.multiSelect,
|
|
946
978
|
node,
|
|
947
|
-
openedIconElement,
|
|
948
979
|
renderChildren,
|
|
949
980
|
tabIndex,
|
|
950
981
|
treeElement,
|
|
@@ -1055,14 +1086,11 @@ export default class TreeElement {
|
|
|
1055
1086
|
private getFocusedNode(): Node | null {
|
|
1056
1087
|
const activeElement = document.activeElement;
|
|
1057
1088
|
|
|
1058
|
-
|
|
1059
|
-
if (
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
// The keyboard must still work for input elements.
|
|
1064
|
-
const tagName = activeElement.tagName;
|
|
1065
|
-
if (tagName !== "A" && tagName !== "LI" && tagName !== "SPAN") {
|
|
1089
|
+
// An active element must exist and the keyboard must still work for input elements.
|
|
1090
|
+
if (
|
|
1091
|
+
!activeElement ||
|
|
1092
|
+
!["A", "LI", "SPAN"].includes(activeElement.tagName)
|
|
1093
|
+
) {
|
|
1066
1094
|
return null;
|
|
1067
1095
|
}
|
|
1068
1096
|
|
|
@@ -1125,6 +1153,9 @@ export default class TreeElement {
|
|
|
1125
1153
|
this.triggerEvent("tree.init");
|
|
1126
1154
|
};
|
|
1127
1155
|
|
|
1156
|
+
// Before the tree is replaced: then the focused node is no longer in it
|
|
1157
|
+
const focusedNode = this.getFocusedNode();
|
|
1158
|
+
|
|
1128
1159
|
this.tree = new this.options.nodeClass(null, true, this.options.nodeClass);
|
|
1129
1160
|
|
|
1130
1161
|
this.selectNodeHandler.clear();
|
|
@@ -1135,6 +1166,10 @@ export default class TreeElement {
|
|
|
1135
1166
|
|
|
1136
1167
|
this.refreshElements(null);
|
|
1137
1168
|
|
|
1169
|
+
if (focusedNode) {
|
|
1170
|
+
this.restoreFocus(focusedNode);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1138
1173
|
if (mustLoadOnDemand) {
|
|
1139
1174
|
// Load data on demand and then init the tree
|
|
1140
1175
|
void this.setInitialStateOnDemand().then(doInit);
|
|
@@ -1143,19 +1178,6 @@ export default class TreeElement {
|
|
|
1143
1178
|
}
|
|
1144
1179
|
}
|
|
1145
1180
|
|
|
1146
|
-
// Does an HTML element of the tree have the focus?
|
|
1147
|
-
private isFocusOnTree(): boolean {
|
|
1148
|
-
return this.getFocusedNode() !== null;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
private isSelectedNodeInSubtree(subtree: Node): boolean {
|
|
1152
|
-
const selectedNode = this.getSelectedNode();
|
|
1153
|
-
|
|
1154
|
-
return selectedNode
|
|
1155
|
-
? subtree === selectedNode || subtree.isParentOf(selectedNode)
|
|
1156
|
-
: false;
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
1181
|
private async loadFolderOnDemand(node: Node, slide: boolean): Promise<void> {
|
|
1160
1182
|
node.is_loading = true;
|
|
1161
1183
|
|
|
@@ -1206,14 +1228,14 @@ export default class TreeElement {
|
|
|
1206
1228
|
return this.dndHandler.mouseStart(positionInfo);
|
|
1207
1229
|
}
|
|
1208
1230
|
|
|
1209
|
-
private mouseStop(positionInfo: PositionInfo):
|
|
1231
|
+
private mouseStop(positionInfo: PositionInfo): void {
|
|
1210
1232
|
/* istanbul ignore if */
|
|
1211
1233
|
if (!this.options.dragAndDrop) {
|
|
1212
|
-
return
|
|
1234
|
+
return;
|
|
1213
1235
|
}
|
|
1214
1236
|
|
|
1215
1237
|
this.scrollHandler.stopScrolling();
|
|
1216
|
-
|
|
1238
|
+
this.dndHandler.mouseStop(positionInfo);
|
|
1217
1239
|
}
|
|
1218
1240
|
|
|
1219
1241
|
private openParents(node: Node) {
|
|
@@ -1229,15 +1251,13 @@ export default class TreeElement {
|
|
|
1229
1251
|
from_node: redraw this subtree
|
|
1230
1252
|
*/
|
|
1231
1253
|
private refreshElements(fromNode: Node | null): void {
|
|
1232
|
-
const
|
|
1233
|
-
const mustSelect = fromNode
|
|
1234
|
-
? this.isSelectedNodeInSubtree(fromNode)
|
|
1235
|
-
: false;
|
|
1254
|
+
const focusedNode = this.getFocusedNode();
|
|
1236
1255
|
|
|
1256
|
+
// The renderer marks the selected nodes, so only the focus needs restoring
|
|
1237
1257
|
this.renderer.render(fromNode);
|
|
1238
1258
|
|
|
1239
|
-
if (
|
|
1240
|
-
this.
|
|
1259
|
+
if (focusedNode) {
|
|
1260
|
+
this.restoreFocus(focusedNode);
|
|
1241
1261
|
}
|
|
1242
1262
|
|
|
1243
1263
|
this.updateTabStop();
|
|
@@ -1245,18 +1265,26 @@ export default class TreeElement {
|
|
|
1245
1265
|
this.triggerEvent("tree.refresh");
|
|
1246
1266
|
}
|
|
1247
1267
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
const node = this.getSelectedNode();
|
|
1254
|
-
if (!node) {
|
|
1268
|
+
// Rendering replaces the elements of the nodes, so a focused element can be
|
|
1269
|
+
// gone. Then move the focus to the new element of the same node. After
|
|
1270
|
+
// loadData the nodes are new objects, so look the node up by its id.
|
|
1271
|
+
private restoreFocus(focusedNode: Node): void {
|
|
1272
|
+
if (this.htmlElement.contains(document.activeElement)) {
|
|
1255
1273
|
return;
|
|
1256
1274
|
}
|
|
1257
1275
|
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1276
|
+
const node =
|
|
1277
|
+
(focusedNode.id == null ? null : this.getNodeById(focusedNode.id)) ??
|
|
1278
|
+
focusedNode;
|
|
1279
|
+
const element = node.element;
|
|
1280
|
+
|
|
1281
|
+
if (element && this.htmlElement.contains(element)) {
|
|
1282
|
+
element.focus();
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
private saveState(): void {
|
|
1287
|
+
this.saveStateHandler.saveState();
|
|
1260
1288
|
}
|
|
1261
1289
|
|
|
1262
1290
|
// Set initial state, either by restoring the state or auto-opening nodes
|
|
@@ -1319,6 +1347,28 @@ export default class TreeElement {
|
|
|
1319
1347
|
}
|
|
1320
1348
|
}
|
|
1321
1349
|
|
|
1350
|
+
/* Change multiSelect after construction. The attributes of the nodes
|
|
1351
|
+
* depend on it, so the tree is rendered again. The selection is kept: a
|
|
1352
|
+
* tree without multiSelect can also have several selected nodes, see
|
|
1353
|
+
* addToSelection.
|
|
1354
|
+
*/
|
|
1355
|
+
private setMultiSelect(multiSelect: boolean): void {
|
|
1356
|
+
const focusedNode = this.getFocusedNode();
|
|
1357
|
+
|
|
1358
|
+
this.keyHandler.setMultiSelect(multiSelect);
|
|
1359
|
+
this.renderer.setMultiSelect(multiSelect);
|
|
1360
|
+
|
|
1361
|
+
if (multiSelect) {
|
|
1362
|
+
this.htmlElement.addEventListener("focusin", this.handleFocusIn);
|
|
1363
|
+
} else {
|
|
1364
|
+
this.htmlElement.removeEventListener("focusin", this.handleFocusIn);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
this.activeNode = multiSelect ? focusedNode : null;
|
|
1368
|
+
this.fallbackTabStop = null;
|
|
1369
|
+
this.refreshElements(null);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1322
1372
|
// Set this HTML element to this node in the node map.
|
|
1323
1373
|
private setNodeElement(element: HTMLElement, node: Node) {
|
|
1324
1374
|
this.nodeMap.set(element, node);
|
|
@@ -1374,6 +1424,6 @@ export {
|
|
|
1374
1424
|
type TreeEvents,
|
|
1375
1425
|
} from "./events";
|
|
1376
1426
|
export { type Node, type NodeData, type NodeId, type Position } from "./node";
|
|
1377
|
-
export { type TreeElementOptions } from "./options";
|
|
1427
|
+
export { type ChangeableOption, type TreeElementOptions } from "./options";
|
|
1378
1428
|
export { type SavedState } from "./saveStateHandler";
|
|
1379
1429
|
export { type SelectNodeOptions } from "./selectNodeHandler";
|
package/src/keyHandler.ts
CHANGED
|
@@ -93,6 +93,26 @@ export default class KeyHandler {
|
|
|
93
93
|
return this.goToNode(this.getCurrentNode()?.getPreviousVisibleNode());
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// Change keyboardSupport after construction
|
|
97
|
+
public setKeyboardSupport(keyboardSupport: boolean): void {
|
|
98
|
+
if (keyboardSupport === this.keyboardSupport) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.keyboardSupport = keyboardSupport;
|
|
103
|
+
|
|
104
|
+
if (keyboardSupport) {
|
|
105
|
+
document.addEventListener("keydown", this.handleKeyDown);
|
|
106
|
+
} else {
|
|
107
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Change multiSelect after construction
|
|
112
|
+
public setMultiSelect(multiSelect: boolean): void {
|
|
113
|
+
this.multiSelect = multiSelect;
|
|
114
|
+
}
|
|
115
|
+
|
|
96
116
|
// Activate the node, for example to open its edit page.
|
|
97
117
|
private activate(node: Node, originalEvent: KeyboardEvent): boolean {
|
|
98
118
|
this.triggerEvent("tree.activate", { node, originalEvent });
|
package/src/mouseHandler.ts
CHANGED
|
@@ -180,7 +180,7 @@ class MouseHandler {
|
|
|
180
180
|
}
|
|
181
181
|
};
|
|
182
182
|
|
|
183
|
-
private handleContextmenu = (e: MouseEvent) => {
|
|
183
|
+
private handleContextmenu = (e: MouseEvent): void => {
|
|
184
184
|
if (!e.target) {
|
|
185
185
|
return;
|
|
186
186
|
}
|
|
@@ -189,21 +189,23 @@ class MouseHandler {
|
|
|
189
189
|
`ul.${this.classNames.tree} .${this.classNames.element}`,
|
|
190
190
|
);
|
|
191
191
|
|
|
192
|
-
if (div) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
e.preventDefault();
|
|
196
|
-
e.stopPropagation();
|
|
192
|
+
if (!div) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
197
195
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
196
|
+
const node = this.getNode(div);
|
|
197
|
+
|
|
198
|
+
if (!node) {
|
|
199
|
+
return;
|
|
204
200
|
}
|
|
205
201
|
|
|
206
|
-
|
|
202
|
+
e.preventDefault();
|
|
203
|
+
e.stopPropagation();
|
|
204
|
+
|
|
205
|
+
this.triggerEvent("tree.contextmenu", {
|
|
206
|
+
node,
|
|
207
|
+
originalEvent: e,
|
|
208
|
+
});
|
|
207
209
|
};
|
|
208
210
|
|
|
209
211
|
private handleDblclick = (e: MouseEvent): void => {
|
package/src/node.ts
CHANGED
|
@@ -28,14 +28,14 @@ type IterateCallback = (node: Node, level: number) => boolean;
|
|
|
28
28
|
*
|
|
29
29
|
* @groupDescription Searching
|
|
30
30
|
* These work on any node, and search that node's subtree. Called on the root
|
|
31
|
-
* node, they search the whole tree
|
|
31
|
+
* node, they search the whole tree—which is what the tree's own methods of
|
|
32
32
|
* the same name do.
|
|
33
33
|
*
|
|
34
34
|
* @groupDescription Changing the tree
|
|
35
35
|
* ::: warning
|
|
36
36
|
* These methods change the data without re-rendering. The tree's
|
|
37
|
-
* [methods](/reference/methods)
|
|
38
|
-
* friends
|
|
37
|
+
* [methods](/reference/methods)—`appendNode`, `removeNode`, `moveNode` and
|
|
38
|
+
* friends—do the same and refresh the display, so prefer those. If you do
|
|
39
39
|
* use these, call `tree.refresh()` afterwards.
|
|
40
40
|
* :::
|
|
41
41
|
*/
|
|
@@ -85,7 +85,7 @@ export class Node {
|
|
|
85
85
|
*/
|
|
86
86
|
public nodeClass?: typeof Node;
|
|
87
87
|
/**
|
|
88
|
-
The parent
|
|
88
|
+
The parent, or `null` for the root node.
|
|
89
89
|
*/
|
|
90
90
|
public parent: Node | null;
|
|
91
91
|
/**
|
|
@@ -191,7 +191,9 @@ export class Node {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
/**
|
|
194
|
-
* Inserts a new parent between this node and its current parent.
|
|
194
|
+
* Inserts a new parent between this node and its current parent. Its
|
|
195
|
+
* siblings move along: the new node takes all children of the current
|
|
196
|
+
* parent, and becomes its only child.
|
|
195
197
|
*
|
|
196
198
|
* @returns The new node, or `null` when the node has no parent.
|
|
197
199
|
* @group Changing the tree
|
|
@@ -268,7 +270,7 @@ export class Node {
|
|
|
268
270
|
* Returns the subtree as plain data, ready for `JSON.stringify`.
|
|
269
271
|
* Internal properties (`parent`, `children`, `element`, `tree`,
|
|
270
272
|
* `idMapping`, `nodeClass`, `load_on_demand`, `isEmptyFolder`) are left
|
|
271
|
-
* out
|
|
273
|
+
* out. Your own properties are kept.
|
|
272
274
|
*
|
|
273
275
|
* ```js
|
|
274
276
|
* tree.getTree().getData();
|
|
@@ -384,7 +386,7 @@ export class Node {
|
|
|
384
386
|
}
|
|
385
387
|
|
|
386
388
|
/**
|
|
387
|
-
* Like `getNextNode`, but skipping nodes inside closed folders
|
|
389
|
+
* Like `getNextNode`, but skipping nodes inside closed folders. This is
|
|
388
390
|
* what the arrow keys use.
|
|
389
391
|
*
|
|
390
392
|
* @group Moving around the tree
|
|
@@ -472,7 +474,7 @@ export class Node {
|
|
|
472
474
|
}
|
|
473
475
|
|
|
474
476
|
/**
|
|
475
|
-
* Returns the parent
|
|
477
|
+
* Returns the parent, or `null` for a top-level node: the root node is not
|
|
476
478
|
* returned.
|
|
477
479
|
*
|
|
478
480
|
* @group Moving around the tree
|
|
@@ -525,8 +527,8 @@ export class Node {
|
|
|
525
527
|
}
|
|
526
528
|
|
|
527
529
|
/**
|
|
528
|
-
* Like `getPreviousNode`, but skipping nodes inside closed folders
|
|
529
|
-
*
|
|
530
|
+
* Like `getPreviousNode`, but skipping nodes inside closed folders.
|
|
531
|
+
* This is what the arrow keys use.
|
|
530
532
|
*
|
|
531
533
|
* @group Moving around the tree
|
|
532
534
|
*/
|
|
@@ -1,33 +1,32 @@
|
|
|
1
1
|
import type { AnimationSpeed } from "../animation";
|
|
2
|
+
import type { FolderIcons } from "../folderOpenState";
|
|
2
3
|
import type { TriggerEvent } from "../methodTypes";
|
|
3
4
|
import type { Node, Position } from "../node";
|
|
4
5
|
import type { NodeElementParams } from "./index";
|
|
5
6
|
|
|
6
7
|
import { slideDown, slideUp } from "../animation";
|
|
8
|
+
import { setFolderLiOpenState, setTogglerOpenState } from "../folderOpenState";
|
|
7
9
|
import NodeElement from "./index";
|
|
8
10
|
|
|
9
11
|
export type RenderChildren = (node: Node) => HTMLUListElement | null;
|
|
10
12
|
|
|
11
13
|
interface FolderElementParams extends NodeElementParams {
|
|
12
|
-
|
|
13
|
-
openedIconElement?: globalThis.Node;
|
|
14
|
+
icons: FolderIcons;
|
|
14
15
|
renderChildren: RenderChildren;
|
|
15
16
|
triggerEvent: TriggerEvent;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
class FolderElement extends NodeElement {
|
|
19
|
-
private
|
|
20
|
-
private openedIconElement?: globalThis.Node;
|
|
20
|
+
private icons: FolderIcons;
|
|
21
21
|
private renderChildren: RenderChildren;
|
|
22
22
|
private triggerEvent: TriggerEvent;
|
|
23
23
|
|
|
24
24
|
constructor({
|
|
25
25
|
classNames,
|
|
26
|
-
closedIconElement,
|
|
27
26
|
getScrollLeft,
|
|
27
|
+
icons,
|
|
28
28
|
multiSelect,
|
|
29
29
|
node,
|
|
30
|
-
openedIconElement,
|
|
31
30
|
renderChildren,
|
|
32
31
|
tabIndex,
|
|
33
32
|
treeElement,
|
|
@@ -42,8 +41,7 @@ class FolderElement extends NodeElement {
|
|
|
42
41
|
treeElement,
|
|
43
42
|
});
|
|
44
43
|
|
|
45
|
-
this.
|
|
46
|
-
this.openedIconElement = openedIconElement;
|
|
44
|
+
this.icons = icons;
|
|
47
45
|
this.renderChildren = renderChildren;
|
|
48
46
|
this.triggerEvent = triggerEvent;
|
|
49
47
|
}
|
|
@@ -58,39 +56,13 @@ class FolderElement extends NodeElement {
|
|
|
58
56
|
|
|
59
57
|
this.node.is_open = false;
|
|
60
58
|
|
|
61
|
-
if (
|
|
62
|
-
this.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const button = this.getButton();
|
|
67
|
-
button.classList.add(this.classNames.closed);
|
|
68
|
-
button.replaceChildren();
|
|
69
|
-
|
|
70
|
-
const closedIconElement = this.closedIconElement;
|
|
71
|
-
|
|
72
|
-
if (closedIconElement) {
|
|
73
|
-
const icon = closedIconElement.cloneNode(true);
|
|
74
|
-
button.append(icon);
|
|
59
|
+
if (this.isRendered()) {
|
|
60
|
+
this.setButtonState(false);
|
|
61
|
+
await this.setUlState(this.getUl(), false, slide, animationSpeed);
|
|
62
|
+
this.setElementState(false);
|
|
75
63
|
}
|
|
76
64
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (ul) {
|
|
80
|
-
if (slide) {
|
|
81
|
-
await slideUp(ul, animationSpeed);
|
|
82
|
-
} else {
|
|
83
|
-
ul.style.display = "none";
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
this.element.classList.add(this.classNames.closed);
|
|
88
|
-
|
|
89
|
-
this.element.setAttribute("aria-expanded", "false");
|
|
90
|
-
|
|
91
|
-
this.triggerEvent("tree.close", {
|
|
92
|
-
node: this.node,
|
|
93
|
-
});
|
|
65
|
+
this.triggerEvent("tree.close", { node: this.node });
|
|
94
66
|
}
|
|
95
67
|
|
|
96
68
|
public async open(
|
|
@@ -103,41 +75,19 @@ class FolderElement extends NodeElement {
|
|
|
103
75
|
|
|
104
76
|
this.node.is_open = true;
|
|
105
77
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const button = this.getButton();
|
|
113
|
-
button.classList.remove(this.classNames.closed);
|
|
114
|
-
button.replaceChildren();
|
|
115
|
-
|
|
116
|
-
const openedIconElement = this.openedIconElement;
|
|
117
|
-
|
|
118
|
-
if (openedIconElement) {
|
|
119
|
-
const icon = openedIconElement.cloneNode(true);
|
|
120
|
-
button.append(icon);
|
|
121
|
-
}
|
|
78
|
+
// A folder that is not rendered is rendered open when its parent is
|
|
79
|
+
// opened
|
|
80
|
+
if (this.isRendered()) {
|
|
81
|
+
this.setButtonState(true);
|
|
122
82
|
|
|
123
|
-
|
|
124
|
-
|
|
83
|
+
// The children are rendered the first time the folder is opened
|
|
84
|
+
const ul = this.getUl() ?? this.renderChildren(this.node);
|
|
85
|
+
await this.setUlState(ul, true, slide, animationSpeed);
|
|
125
86
|
|
|
126
|
-
|
|
127
|
-
if (slide) {
|
|
128
|
-
await slideDown(ul, animationSpeed);
|
|
129
|
-
} else {
|
|
130
|
-
ul.style.display = "block";
|
|
131
|
-
}
|
|
87
|
+
this.setElementState(true);
|
|
132
88
|
}
|
|
133
89
|
|
|
134
|
-
this.
|
|
135
|
-
|
|
136
|
-
this.element.setAttribute("aria-expanded", "true");
|
|
137
|
-
|
|
138
|
-
this.triggerEvent("tree.open", {
|
|
139
|
-
node: this.node,
|
|
140
|
-
});
|
|
90
|
+
this.triggerEvent("tree.open", { node: this.node });
|
|
141
91
|
}
|
|
142
92
|
|
|
143
93
|
protected mustShowBorderDropHint(position: Position): boolean {
|
|
@@ -149,6 +99,31 @@ class FolderElement extends NodeElement {
|
|
|
149
99
|
`:scope > .${this.classNames.element} > .${this.classNames.toggler}`,
|
|
150
100
|
) as HTMLElement;
|
|
151
101
|
}
|
|
102
|
+
|
|
103
|
+
private setButtonState(isOpen: boolean): void {
|
|
104
|
+
setTogglerOpenState(this.getButton(), isOpen, this.classNames, this.icons);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private setElementState(isOpen: boolean): void {
|
|
108
|
+
setFolderLiOpenState(this.element, isOpen, this.classNames);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private async setUlState(
|
|
112
|
+
ul: HTMLUListElement | null,
|
|
113
|
+
isOpen: boolean,
|
|
114
|
+
slide: boolean,
|
|
115
|
+
animationSpeed: AnimationSpeed,
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
if (!ul) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (slide) {
|
|
122
|
+
await (isOpen ? slideDown : slideUp)(ul, animationSpeed);
|
|
123
|
+
} else {
|
|
124
|
+
ul.style.display = isOpen ? "block" : "none";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
152
127
|
}
|
|
153
128
|
|
|
154
129
|
export default FolderElement;
|