@headless-tree/core 0.0.0-20260108010329 → 0.0.0-20260109213608
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/CHANGELOG.md +7 -1
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +12 -7
- package/dist/index.mjs +12 -7
- package/package.json +1 -1
- package/src/features/drag-and-drop/drag-and-drop.spec.ts +94 -0
- package/src/features/drag-and-drop/feature.ts +6 -2
- package/src/features/drag-and-drop/types.ts +4 -0
- package/src/features/hotkeys-core/feature.ts +11 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# @headless-tree/core
|
|
2
2
|
|
|
3
|
-
## 0.0.0-
|
|
3
|
+
## 0.0.0-20260109213608
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4397d8c: Added `draggedItemOverwritesSelection` as config option to the Drag Feature. Setting it to false will disable the current default behavior, where dragging an unselected item will overwrite the selection to just the dragged item.
|
|
8
|
+
|
|
9
|
+
## 1.6.2
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
6
12
|
|
package/dist/index.d.mts
CHANGED
|
@@ -70,6 +70,9 @@ type DragAndDropFeatureDef<T> = {
|
|
|
70
70
|
openOnDropDelay?: number;
|
|
71
71
|
/** If true, `item.getProps()` will not include drag event handlers. Use `item.getDragHandleProps()` on the handler element. */
|
|
72
72
|
seperateDragHandle?: boolean;
|
|
73
|
+
/** If true, the item that is dragged is not selected, the selected items will be overwritten to just the dragged item.
|
|
74
|
+
* Defaults to true */
|
|
75
|
+
draggedItemOverwritesSelection?: boolean;
|
|
73
76
|
};
|
|
74
77
|
treeInstance: {
|
|
75
78
|
getDragTarget: () => DragTarget<T> | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -70,6 +70,9 @@ type DragAndDropFeatureDef<T> = {
|
|
|
70
70
|
openOnDropDelay?: number;
|
|
71
71
|
/** If true, `item.getProps()` will not include drag event handlers. Use `item.getDragHandleProps()` on the handler element. */
|
|
72
72
|
seperateDragHandle?: boolean;
|
|
73
|
+
/** If true, the item that is dragged is not selected, the selected items will be overwritten to just the dragged item.
|
|
74
|
+
* Defaults to true */
|
|
75
|
+
draggedItemOverwritesSelection?: boolean;
|
|
73
76
|
};
|
|
74
77
|
treeInstance: {
|
|
75
78
|
getDragTarget: () => DragTarget<T> | null;
|
package/dist/index.js
CHANGED
|
@@ -1025,6 +1025,7 @@ var checkboxesFeature = {
|
|
|
1025
1025
|
};
|
|
1026
1026
|
|
|
1027
1027
|
// src/features/hotkeys-core/feature.ts
|
|
1028
|
+
var resolveKeyCode = (e) => e.code !== "" && e.code !== "Unidentified" ? e.code : e.key;
|
|
1028
1029
|
var specialKeys = {
|
|
1029
1030
|
// TODO:breaking deprecate auto-lowercase
|
|
1030
1031
|
letter: /^Key[A-Z]$/,
|
|
@@ -1073,9 +1074,10 @@ var hotkeysCoreFeature = {
|
|
|
1073
1074
|
if (e.target instanceof HTMLInputElement && ignoreHotkeysOnInputs) {
|
|
1074
1075
|
return;
|
|
1075
1076
|
}
|
|
1077
|
+
const resolvedCode = resolveKeyCode(e);
|
|
1076
1078
|
(_b = (_a = data.current).pressedKeys) != null ? _b : _a.pressedKeys = /* @__PURE__ */ new Set();
|
|
1077
|
-
const newMatch = !data.current.pressedKeys.has(
|
|
1078
|
-
data.current.pressedKeys.add(
|
|
1079
|
+
const newMatch = !data.current.pressedKeys.has(resolvedCode);
|
|
1080
|
+
data.current.pressedKeys.add(resolvedCode);
|
|
1079
1081
|
const hotkeyName = findHotkeyMatch(
|
|
1080
1082
|
data.current.pressedKeys,
|
|
1081
1083
|
tree,
|
|
@@ -1083,7 +1085,7 @@ var hotkeysCoreFeature = {
|
|
|
1083
1085
|
hotkeys
|
|
1084
1086
|
);
|
|
1085
1087
|
if (e.target instanceof HTMLInputElement) {
|
|
1086
|
-
data.current.pressedKeys.delete(
|
|
1088
|
+
data.current.pressedKeys.delete(resolvedCode);
|
|
1087
1089
|
}
|
|
1088
1090
|
if (!hotkeyName) return;
|
|
1089
1091
|
const hotkeyConfig = __spreadValues(__spreadValues({}, tree.getHotkeyPresets()[hotkeyName]), hotkeys == null ? void 0 : hotkeys[hotkeyName]);
|
|
@@ -1098,7 +1100,7 @@ var hotkeysCoreFeature = {
|
|
|
1098
1100
|
const keyup = (e) => {
|
|
1099
1101
|
var _a, _b;
|
|
1100
1102
|
(_b = (_a = data.current).pressedKeys) != null ? _b : _a.pressedKeys = /* @__PURE__ */ new Set();
|
|
1101
|
-
data.current.pressedKeys.delete(e
|
|
1103
|
+
data.current.pressedKeys.delete(resolveKeyCode(e));
|
|
1102
1104
|
};
|
|
1103
1105
|
const reset = () => {
|
|
1104
1106
|
data.current.pressedKeys = /* @__PURE__ */ new Set();
|
|
@@ -1527,7 +1529,8 @@ var dragAndDropFeature = {
|
|
|
1527
1529
|
canDragForeignDragObjectOver: defaultConfig.canDropForeignDragObject !== defaultCanDropForeignDragObject ? (dataTransfer) => dataTransfer.effectAllowed !== "none" : () => false,
|
|
1528
1530
|
setDndState: makeStateUpdater("dnd", tree),
|
|
1529
1531
|
canReorder: true,
|
|
1530
|
-
openOnDropDelay: 800
|
|
1532
|
+
openOnDropDelay: 800,
|
|
1533
|
+
draggedItemOverwritesSelection: true
|
|
1531
1534
|
}, defaultConfig),
|
|
1532
1535
|
stateHandlerNames: {
|
|
1533
1536
|
dnd: "setDndState"
|
|
@@ -1697,10 +1700,12 @@ var dragAndDropFeature = {
|
|
|
1697
1700
|
draggable: true,
|
|
1698
1701
|
onDragStart: (e) => {
|
|
1699
1702
|
var _a, _b, _c, _d;
|
|
1703
|
+
const { draggedItemOverwritesSelection } = tree.getConfig();
|
|
1700
1704
|
const selectedItems = tree.getSelectedItems ? tree.getSelectedItems() : [tree.getFocusedItem()];
|
|
1701
|
-
const
|
|
1705
|
+
const overwriteSelection = !selectedItems.includes(item) && draggedItemOverwritesSelection;
|
|
1706
|
+
const items = overwriteSelection ? [item] : selectedItems;
|
|
1702
1707
|
const config = tree.getConfig();
|
|
1703
|
-
if (
|
|
1708
|
+
if (overwriteSelection) {
|
|
1704
1709
|
(_a = tree.setSelectedItems) == null ? void 0 : _a.call(tree, [item.getItemMeta().itemId]);
|
|
1705
1710
|
}
|
|
1706
1711
|
if (!((_c = (_b = config.canDrag) == null ? void 0 : _b.call(config, items)) != null ? _c : true)) {
|
package/dist/index.mjs
CHANGED
|
@@ -981,6 +981,7 @@ var checkboxesFeature = {
|
|
|
981
981
|
};
|
|
982
982
|
|
|
983
983
|
// src/features/hotkeys-core/feature.ts
|
|
984
|
+
var resolveKeyCode = (e) => e.code !== "" && e.code !== "Unidentified" ? e.code : e.key;
|
|
984
985
|
var specialKeys = {
|
|
985
986
|
// TODO:breaking deprecate auto-lowercase
|
|
986
987
|
letter: /^Key[A-Z]$/,
|
|
@@ -1029,9 +1030,10 @@ var hotkeysCoreFeature = {
|
|
|
1029
1030
|
if (e.target instanceof HTMLInputElement && ignoreHotkeysOnInputs) {
|
|
1030
1031
|
return;
|
|
1031
1032
|
}
|
|
1033
|
+
const resolvedCode = resolveKeyCode(e);
|
|
1032
1034
|
(_b = (_a = data.current).pressedKeys) != null ? _b : _a.pressedKeys = /* @__PURE__ */ new Set();
|
|
1033
|
-
const newMatch = !data.current.pressedKeys.has(
|
|
1034
|
-
data.current.pressedKeys.add(
|
|
1035
|
+
const newMatch = !data.current.pressedKeys.has(resolvedCode);
|
|
1036
|
+
data.current.pressedKeys.add(resolvedCode);
|
|
1035
1037
|
const hotkeyName = findHotkeyMatch(
|
|
1036
1038
|
data.current.pressedKeys,
|
|
1037
1039
|
tree,
|
|
@@ -1039,7 +1041,7 @@ var hotkeysCoreFeature = {
|
|
|
1039
1041
|
hotkeys
|
|
1040
1042
|
);
|
|
1041
1043
|
if (e.target instanceof HTMLInputElement) {
|
|
1042
|
-
data.current.pressedKeys.delete(
|
|
1044
|
+
data.current.pressedKeys.delete(resolvedCode);
|
|
1043
1045
|
}
|
|
1044
1046
|
if (!hotkeyName) return;
|
|
1045
1047
|
const hotkeyConfig = __spreadValues(__spreadValues({}, tree.getHotkeyPresets()[hotkeyName]), hotkeys == null ? void 0 : hotkeys[hotkeyName]);
|
|
@@ -1054,7 +1056,7 @@ var hotkeysCoreFeature = {
|
|
|
1054
1056
|
const keyup = (e) => {
|
|
1055
1057
|
var _a, _b;
|
|
1056
1058
|
(_b = (_a = data.current).pressedKeys) != null ? _b : _a.pressedKeys = /* @__PURE__ */ new Set();
|
|
1057
|
-
data.current.pressedKeys.delete(e
|
|
1059
|
+
data.current.pressedKeys.delete(resolveKeyCode(e));
|
|
1058
1060
|
};
|
|
1059
1061
|
const reset = () => {
|
|
1060
1062
|
data.current.pressedKeys = /* @__PURE__ */ new Set();
|
|
@@ -1483,7 +1485,8 @@ var dragAndDropFeature = {
|
|
|
1483
1485
|
canDragForeignDragObjectOver: defaultConfig.canDropForeignDragObject !== defaultCanDropForeignDragObject ? (dataTransfer) => dataTransfer.effectAllowed !== "none" : () => false,
|
|
1484
1486
|
setDndState: makeStateUpdater("dnd", tree),
|
|
1485
1487
|
canReorder: true,
|
|
1486
|
-
openOnDropDelay: 800
|
|
1488
|
+
openOnDropDelay: 800,
|
|
1489
|
+
draggedItemOverwritesSelection: true
|
|
1487
1490
|
}, defaultConfig),
|
|
1488
1491
|
stateHandlerNames: {
|
|
1489
1492
|
dnd: "setDndState"
|
|
@@ -1653,10 +1656,12 @@ var dragAndDropFeature = {
|
|
|
1653
1656
|
draggable: true,
|
|
1654
1657
|
onDragStart: (e) => {
|
|
1655
1658
|
var _a, _b, _c, _d;
|
|
1659
|
+
const { draggedItemOverwritesSelection } = tree.getConfig();
|
|
1656
1660
|
const selectedItems = tree.getSelectedItems ? tree.getSelectedItems() : [tree.getFocusedItem()];
|
|
1657
|
-
const
|
|
1661
|
+
const overwriteSelection = !selectedItems.includes(item) && draggedItemOverwritesSelection;
|
|
1662
|
+
const items = overwriteSelection ? [item] : selectedItems;
|
|
1658
1663
|
const config = tree.getConfig();
|
|
1659
|
-
if (
|
|
1664
|
+
if (overwriteSelection) {
|
|
1660
1665
|
(_a = tree.setSelectedItems) == null ? void 0 : _a.call(tree, [item.getItemMeta().itemId]);
|
|
1661
1666
|
}
|
|
1662
1667
|
if (!((_c = (_b = config.canDrag) == null ? void 0 : _b.call(config, items)) != null ? _c : true)) {
|
package/package.json
CHANGED
|
@@ -787,5 +787,99 @@ describe("core-feature/drag-and-drop", () => {
|
|
|
787
787
|
expect(canDrop).toBeCalledTimes(3);
|
|
788
788
|
});
|
|
789
789
|
});
|
|
790
|
+
|
|
791
|
+
describe("draggedItemOverwritesSelection", () => {
|
|
792
|
+
it("overwrites selection when dragging unselected item with draggedItemOverwritesSelection=true", () => {
|
|
793
|
+
tree.do.selectItem("x111");
|
|
794
|
+
tree.do.ctrlSelectItem("x112");
|
|
795
|
+
tree.do.ctrlSelectItem("x113");
|
|
796
|
+
|
|
797
|
+
expect(tree.instance.getItemInstance("x111").isSelected()).toBe(true);
|
|
798
|
+
expect(tree.instance.getItemInstance("x112").isSelected()).toBe(true);
|
|
799
|
+
expect(tree.instance.getItemInstance("x113").isSelected()).toBe(true);
|
|
800
|
+
expect(tree.instance.getItemInstance("x114").isSelected()).toBe(false);
|
|
801
|
+
|
|
802
|
+
tree.do.startDrag("x114");
|
|
803
|
+
|
|
804
|
+
expect(tree.instance.getItemInstance("x111").isSelected()).toBe(false);
|
|
805
|
+
expect(tree.instance.getItemInstance("x112").isSelected()).toBe(false);
|
|
806
|
+
expect(tree.instance.getItemInstance("x113").isSelected()).toBe(false);
|
|
807
|
+
expect(tree.instance.getItemInstance("x114").isSelected()).toBe(true);
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
it("preserves selection when dragging unselected item with draggedItemOverwritesSelection=false", async () => {
|
|
811
|
+
const testTree = await tree
|
|
812
|
+
.with({ draggedItemOverwritesSelection: false })
|
|
813
|
+
.createTestCaseTree();
|
|
814
|
+
|
|
815
|
+
testTree.do.selectItem("x111");
|
|
816
|
+
testTree.do.ctrlSelectItem("x112");
|
|
817
|
+
testTree.do.ctrlSelectItem("x113");
|
|
818
|
+
|
|
819
|
+
expect(testTree.item("x111").isSelected()).toBe(true);
|
|
820
|
+
expect(testTree.item("x112").isSelected()).toBe(true);
|
|
821
|
+
expect(testTree.item("x113").isSelected()).toBe(true);
|
|
822
|
+
expect(testTree.item("x114").isSelected()).toBe(false);
|
|
823
|
+
testTree.do.startDrag("x114");
|
|
824
|
+
|
|
825
|
+
expect(testTree.item("x111").isSelected()).toBe(true);
|
|
826
|
+
expect(testTree.item("x112").isSelected()).toBe(true);
|
|
827
|
+
expect(testTree.item("x113").isSelected()).toBe(true);
|
|
828
|
+
expect(testTree.item("x114").isSelected()).toBe(false);
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
it("does not overwrite selection when dragging selected item with draggedItemOverwritesSelection=true", () => {
|
|
832
|
+
tree.do.selectItem("x111");
|
|
833
|
+
tree.do.ctrlSelectItem("x112");
|
|
834
|
+
tree.do.ctrlSelectItem("x113");
|
|
835
|
+
|
|
836
|
+
expect(tree.item("x111").isSelected()).toBe(true);
|
|
837
|
+
expect(tree.item("x112").isSelected()).toBe(true);
|
|
838
|
+
expect(tree.item("x113").isSelected()).toBe(true);
|
|
839
|
+
|
|
840
|
+
tree.do.startDrag("x111");
|
|
841
|
+
|
|
842
|
+
expect(tree.item("x111").isSelected()).toBe(true);
|
|
843
|
+
expect(tree.item("x112").isSelected()).toBe(true);
|
|
844
|
+
expect(tree.item("x113").isSelected()).toBe(true);
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
it("does not overwrite selection when dragging selected item with draggedItemOverwritesSelection=false", async () => {
|
|
848
|
+
const testTree = await tree
|
|
849
|
+
.with({ draggedItemOverwritesSelection: false })
|
|
850
|
+
.createTestCaseTree();
|
|
851
|
+
|
|
852
|
+
testTree.do.selectItem("x111");
|
|
853
|
+
testTree.do.ctrlSelectItem("x112");
|
|
854
|
+
testTree.do.ctrlSelectItem("x113");
|
|
855
|
+
|
|
856
|
+
expect(testTree.item("x111").isSelected()).toBe(true);
|
|
857
|
+
expect(testTree.item("x112").isSelected()).toBe(true);
|
|
858
|
+
expect(testTree.item("x113").isSelected()).toBe(true);
|
|
859
|
+
|
|
860
|
+
testTree.do.startDrag("x111");
|
|
861
|
+
|
|
862
|
+
expect(testTree.item("x111").isSelected()).toBe(true);
|
|
863
|
+
expect(testTree.item("x112").isSelected()).toBe(true);
|
|
864
|
+
expect(testTree.item("x113").isSelected()).toBe(true);
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
it("drags all selected items when draggedItemOverwritesSelection=false", async () => {
|
|
868
|
+
const testTree = await tree
|
|
869
|
+
.with({ draggedItemOverwritesSelection: false })
|
|
870
|
+
.createTestCaseTree();
|
|
871
|
+
|
|
872
|
+
testTree.do.selectItem("x111");
|
|
873
|
+
testTree.do.ctrlSelectItem("x112");
|
|
874
|
+
testTree.do.ctrlSelectItem("x113");
|
|
875
|
+
|
|
876
|
+
testTree.do.startDrag("x111");
|
|
877
|
+
testTree.do.dragOverAndDrop("x21");
|
|
878
|
+
|
|
879
|
+
testTree.expect.dropped(["x111", "x112", "x113"], {
|
|
880
|
+
item: testTree.item("x21"),
|
|
881
|
+
});
|
|
882
|
+
});
|
|
883
|
+
});
|
|
790
884
|
});
|
|
791
885
|
});
|
|
@@ -57,6 +57,7 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
57
57
|
setDndState: makeStateUpdater("dnd", tree),
|
|
58
58
|
canReorder: true,
|
|
59
59
|
openOnDropDelay: 800,
|
|
60
|
+
draggedItemOverwritesSelection: true,
|
|
60
61
|
...defaultConfig,
|
|
61
62
|
}),
|
|
62
63
|
|
|
@@ -280,13 +281,16 @@ export const dragAndDropFeature: FeatureImplementation = {
|
|
|
280
281
|
draggable: true,
|
|
281
282
|
|
|
282
283
|
onDragStart: (e: DragEvent) => {
|
|
284
|
+
const { draggedItemOverwritesSelection } = tree.getConfig();
|
|
283
285
|
const selectedItems = tree.getSelectedItems
|
|
284
286
|
? tree.getSelectedItems()
|
|
285
287
|
: [tree.getFocusedItem()];
|
|
286
|
-
const
|
|
288
|
+
const overwriteSelection =
|
|
289
|
+
!selectedItems.includes(item) && draggedItemOverwritesSelection;
|
|
290
|
+
const items = overwriteSelection ? [item] : selectedItems;
|
|
287
291
|
const config = tree.getConfig();
|
|
288
292
|
|
|
289
|
-
if (
|
|
293
|
+
if (overwriteSelection) {
|
|
290
294
|
tree.setSelectedItems?.([item.getItemMeta().itemId]);
|
|
291
295
|
}
|
|
292
296
|
|
|
@@ -100,6 +100,10 @@ export type DragAndDropFeatureDef<T> = {
|
|
|
100
100
|
|
|
101
101
|
/** If true, `item.getProps()` will not include drag event handlers. Use `item.getDragHandleProps()` on the handler element. */
|
|
102
102
|
seperateDragHandle?: boolean;
|
|
103
|
+
|
|
104
|
+
/** If true, the item that is dragged is not selected, the selected items will be overwritten to just the dragged item.
|
|
105
|
+
* Defaults to true */
|
|
106
|
+
draggedItemOverwritesSelection?: boolean;
|
|
103
107
|
};
|
|
104
108
|
treeInstance: {
|
|
105
109
|
getDragTarget: () => DragTarget<T> | null;
|
|
@@ -5,6 +5,11 @@ import {
|
|
|
5
5
|
} from "../../types/core";
|
|
6
6
|
import { HotkeyConfig, HotkeysCoreDataRef } from "./types";
|
|
7
7
|
|
|
8
|
+
// e.code may be empty or "Unidentified" on mobile virtual keyboards
|
|
9
|
+
// or during IME composition, so we fall back to e.key
|
|
10
|
+
const resolveKeyCode = (e: KeyboardEvent): string =>
|
|
11
|
+
e.code !== "" && e.code !== "Unidentified" ? e.code : e.key;
|
|
12
|
+
|
|
8
13
|
const specialKeys: Record<string, RegExp> = {
|
|
9
14
|
// TODO:breaking deprecate auto-lowercase
|
|
10
15
|
letter: /^Key[A-Z]$/,
|
|
@@ -71,9 +76,11 @@ export const hotkeysCoreFeature: FeatureImplementation = {
|
|
|
71
76
|
return;
|
|
72
77
|
}
|
|
73
78
|
|
|
79
|
+
const resolvedCode = resolveKeyCode(e);
|
|
80
|
+
|
|
74
81
|
data.current.pressedKeys ??= new Set();
|
|
75
|
-
const newMatch = !data.current.pressedKeys.has(
|
|
76
|
-
data.current.pressedKeys.add(
|
|
82
|
+
const newMatch = !data.current.pressedKeys.has(resolvedCode);
|
|
83
|
+
data.current.pressedKeys.add(resolvedCode);
|
|
77
84
|
|
|
78
85
|
const hotkeyName = findHotkeyMatch(
|
|
79
86
|
data.current.pressedKeys,
|
|
@@ -85,7 +92,7 @@ export const hotkeysCoreFeature: FeatureImplementation = {
|
|
|
85
92
|
if (e.target instanceof HTMLInputElement) {
|
|
86
93
|
// JS respects composite keydowns while input elements are focused, and
|
|
87
94
|
// doesnt send the associated keyup events with the same key name
|
|
88
|
-
data.current.pressedKeys.delete(
|
|
95
|
+
data.current.pressedKeys.delete(resolvedCode);
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
if (!hotkeyName) return;
|
|
@@ -110,7 +117,7 @@ export const hotkeysCoreFeature: FeatureImplementation = {
|
|
|
110
117
|
|
|
111
118
|
const keyup = (e: KeyboardEvent) => {
|
|
112
119
|
data.current.pressedKeys ??= new Set();
|
|
113
|
-
data.current.pressedKeys.delete(e
|
|
120
|
+
data.current.pressedKeys.delete(resolveKeyCode(e));
|
|
114
121
|
};
|
|
115
122
|
|
|
116
123
|
const reset = () => {
|