@headless-tree/core 0.0.0-20250511190858 → 0.0.0-20250511194715

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 CHANGED
@@ -1,10 +1,11 @@
1
1
  # @headless-tree/core
2
2
 
3
- ## 0.0.0-20250511190858
3
+ ## 0.0.0-20250511194715
4
4
 
5
5
  ### Minor Changes
6
6
 
7
7
  - 64d8e2a: add getChildrenWithData method to data loader to support fetching all children of an item at once
8
+ - 35260e3: fixed hotkey issues where releasing modifier keys (like shift) before normal keys can cause issues with subsequent keydown events
8
9
 
9
10
  ### Patch Changes
10
11
 
@@ -51,7 +51,7 @@ exports.expandAllFeature = {
51
51
  handler: (_, tree) => __awaiter(void 0, void 0, void 0, function* () {
52
52
  const cancelToken = { current: false };
53
53
  const cancelHandler = (e) => {
54
- if (e.key === "Escape") {
54
+ if (e.code === "Escape") {
55
55
  cancelToken.current = true;
56
56
  }
57
57
  };
@@ -61,7 +61,7 @@ exports.expandAllFeature = {
61
61
  }),
62
62
  },
63
63
  collapseSelected: {
64
- hotkey: "Control+Shift+-",
64
+ hotkey: "Control+Shift+Minus",
65
65
  handler: (_, tree) => {
66
66
  tree.getSelectedItems().forEach((item) => item.collapseAll());
67
67
  },
@@ -2,16 +2,31 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.hotkeysCoreFeature = void 0;
4
4
  const specialKeys = {
5
- Letter: /^[a-z]$/,
6
- LetterOrNumber: /^[a-z0-9]$/,
7
- Plus: /^\+$/,
8
- Space: /^ $/,
5
+ // TODO:breaking deprecate auto-lowercase
6
+ letter: /^Key[A-Z]$/,
7
+ letterornumber: /^(Key[A-Z]|Digit[0-9])$/,
8
+ plus: /^(NumpadAdd|Plus)$/,
9
+ minus: /^(NumpadSubtract|Minus)$/,
10
+ control: /^(ControlLeft|ControlRight)$/,
11
+ shift: /^(ShiftLeft|ShiftRight)$/,
9
12
  };
10
13
  const testHotkeyMatch = (pressedKeys, tree, hotkey) => {
11
- const supposedKeys = hotkey.hotkey.toLowerCase().split("+");
12
- const doKeysMatch = supposedKeys.every((key) => key in specialKeys
13
- ? [...pressedKeys].some((pressedKey) => specialKeys[key].test(pressedKey))
14
- : pressedKeys.has(key));
14
+ const supposedKeys = hotkey.hotkey.toLowerCase().split("+"); // TODO:breaking deprecate auto-lowercase
15
+ const doKeysMatch = supposedKeys.every((key) => {
16
+ if (key in specialKeys) {
17
+ return [...pressedKeys].some((pressedKey) => specialKeys[key].test(pressedKey));
18
+ }
19
+ const pressedKeysLowerCase = [...pressedKeys] // TODO:breaking deprecate auto-lowercase
20
+ .map((k) => k.toLowerCase());
21
+ if (pressedKeysLowerCase.includes(key.toLowerCase())) {
22
+ return true;
23
+ }
24
+ if (pressedKeysLowerCase.includes(`key${key.toLowerCase()}`)) {
25
+ // TODO:breaking deprecate e.key character matching
26
+ return true;
27
+ }
28
+ return false;
29
+ });
15
30
  const isEnabled = !hotkey.isEnabled || hotkey.isEnabled(tree);
16
31
  const equalCounts = pressedKeys.size === supposedKeys.length;
17
32
  return doKeysMatch && isEnabled && equalCounts;
@@ -31,15 +46,14 @@ exports.hotkeysCoreFeature = {
31
46
  if (e.target instanceof HTMLInputElement && ignoreHotkeysOnInputs) {
32
47
  return;
33
48
  }
34
- const key = e.key.toLowerCase();
35
49
  (_a = (_b = data.current).pressedKeys) !== null && _a !== void 0 ? _a : (_b.pressedKeys = new Set());
36
- const newMatch = !data.current.pressedKeys.has(key);
37
- data.current.pressedKeys.add(key);
50
+ const newMatch = !data.current.pressedKeys.has(e.code);
51
+ data.current.pressedKeys.add(e.code);
38
52
  const hotkeyName = findHotkeyMatch(data.current.pressedKeys, tree, tree.getHotkeyPresets(), hotkeys);
39
53
  if (e.target instanceof HTMLInputElement) {
40
54
  // JS respects composite keydowns while input elements are focused, and
41
55
  // doesnt send the associated keyup events with the same key name
42
- data.current.pressedKeys.delete(key);
56
+ data.current.pressedKeys.delete(e.code);
43
57
  }
44
58
  if (!hotkeyName)
45
59
  return;
@@ -60,7 +74,7 @@ exports.hotkeysCoreFeature = {
60
74
  var _a;
61
75
  var _b;
62
76
  (_a = (_b = data.current).pressedKeys) !== null && _a !== void 0 ? _a : (_b.pressedKeys = new Set());
63
- data.current.pressedKeys.delete(e.key.toLowerCase());
77
+ data.current.pressedKeys.delete(e.code);
64
78
  };
65
79
  const reset = () => {
66
80
  data.current.pressedKeys = new Set();
@@ -144,7 +144,7 @@ exports.keyboardDragAndDropFeature = {
144
144
  },
145
145
  hotkeys: {
146
146
  startDrag: {
147
- hotkey: "Control+Shift+D",
147
+ hotkey: "Control+Shift+KeyD",
148
148
  preventDefault: true,
149
149
  isEnabled: (tree) => !tree.getState().dnd,
150
150
  handler: (_, tree) => {
@@ -122,7 +122,7 @@ exports.selectionFeature = {
122
122
  },
123
123
  },
124
124
  selectAll: {
125
- hotkey: "Control+a",
125
+ hotkey: "Control+KeyA",
126
126
  preventDefault: true,
127
127
  handler: (e, tree) => {
128
128
  tree.setSelectedItems(tree.getItems().map((item) => item.getId()));
@@ -48,7 +48,7 @@ export const expandAllFeature = {
48
48
  handler: (_, tree) => __awaiter(void 0, void 0, void 0, function* () {
49
49
  const cancelToken = { current: false };
50
50
  const cancelHandler = (e) => {
51
- if (e.key === "Escape") {
51
+ if (e.code === "Escape") {
52
52
  cancelToken.current = true;
53
53
  }
54
54
  };
@@ -58,7 +58,7 @@ export const expandAllFeature = {
58
58
  }),
59
59
  },
60
60
  collapseSelected: {
61
- hotkey: "Control+Shift+-",
61
+ hotkey: "Control+Shift+Minus",
62
62
  handler: (_, tree) => {
63
63
  tree.getSelectedItems().forEach((item) => item.collapseAll());
64
64
  },
@@ -1,14 +1,29 @@
1
1
  const specialKeys = {
2
- Letter: /^[a-z]$/,
3
- LetterOrNumber: /^[a-z0-9]$/,
4
- Plus: /^\+$/,
5
- Space: /^ $/,
2
+ // TODO:breaking deprecate auto-lowercase
3
+ letter: /^Key[A-Z]$/,
4
+ letterornumber: /^(Key[A-Z]|Digit[0-9])$/,
5
+ plus: /^(NumpadAdd|Plus)$/,
6
+ minus: /^(NumpadSubtract|Minus)$/,
7
+ control: /^(ControlLeft|ControlRight)$/,
8
+ shift: /^(ShiftLeft|ShiftRight)$/,
6
9
  };
7
10
  const testHotkeyMatch = (pressedKeys, tree, hotkey) => {
8
- const supposedKeys = hotkey.hotkey.toLowerCase().split("+");
9
- const doKeysMatch = supposedKeys.every((key) => key in specialKeys
10
- ? [...pressedKeys].some((pressedKey) => specialKeys[key].test(pressedKey))
11
- : pressedKeys.has(key));
11
+ const supposedKeys = hotkey.hotkey.toLowerCase().split("+"); // TODO:breaking deprecate auto-lowercase
12
+ const doKeysMatch = supposedKeys.every((key) => {
13
+ if (key in specialKeys) {
14
+ return [...pressedKeys].some((pressedKey) => specialKeys[key].test(pressedKey));
15
+ }
16
+ const pressedKeysLowerCase = [...pressedKeys] // TODO:breaking deprecate auto-lowercase
17
+ .map((k) => k.toLowerCase());
18
+ if (pressedKeysLowerCase.includes(key.toLowerCase())) {
19
+ return true;
20
+ }
21
+ if (pressedKeysLowerCase.includes(`key${key.toLowerCase()}`)) {
22
+ // TODO:breaking deprecate e.key character matching
23
+ return true;
24
+ }
25
+ return false;
26
+ });
12
27
  const isEnabled = !hotkey.isEnabled || hotkey.isEnabled(tree);
13
28
  const equalCounts = pressedKeys.size === supposedKeys.length;
14
29
  return doKeysMatch && isEnabled && equalCounts;
@@ -28,15 +43,14 @@ export const hotkeysCoreFeature = {
28
43
  if (e.target instanceof HTMLInputElement && ignoreHotkeysOnInputs) {
29
44
  return;
30
45
  }
31
- const key = e.key.toLowerCase();
32
46
  (_a = (_b = data.current).pressedKeys) !== null && _a !== void 0 ? _a : (_b.pressedKeys = new Set());
33
- const newMatch = !data.current.pressedKeys.has(key);
34
- data.current.pressedKeys.add(key);
47
+ const newMatch = !data.current.pressedKeys.has(e.code);
48
+ data.current.pressedKeys.add(e.code);
35
49
  const hotkeyName = findHotkeyMatch(data.current.pressedKeys, tree, tree.getHotkeyPresets(), hotkeys);
36
50
  if (e.target instanceof HTMLInputElement) {
37
51
  // JS respects composite keydowns while input elements are focused, and
38
52
  // doesnt send the associated keyup events with the same key name
39
- data.current.pressedKeys.delete(key);
53
+ data.current.pressedKeys.delete(e.code);
40
54
  }
41
55
  if (!hotkeyName)
42
56
  return;
@@ -57,7 +71,7 @@ export const hotkeysCoreFeature = {
57
71
  var _a;
58
72
  var _b;
59
73
  (_a = (_b = data.current).pressedKeys) !== null && _a !== void 0 ? _a : (_b.pressedKeys = new Set());
60
- data.current.pressedKeys.delete(e.key.toLowerCase());
74
+ data.current.pressedKeys.delete(e.code);
61
75
  };
62
76
  const reset = () => {
63
77
  data.current.pressedKeys = new Set();
@@ -141,7 +141,7 @@ export const keyboardDragAndDropFeature = {
141
141
  },
142
142
  hotkeys: {
143
143
  startDrag: {
144
- hotkey: "Control+Shift+D",
144
+ hotkey: "Control+Shift+KeyD",
145
145
  preventDefault: true,
146
146
  isEnabled: (tree) => !tree.getState().dnd,
147
147
  handler: (_, tree) => {
@@ -119,7 +119,7 @@ export const selectionFeature = {
119
119
  },
120
120
  },
121
121
  selectAll: {
122
- hotkey: "Control+a",
122
+ hotkey: "Control+KeyA",
123
123
  preventDefault: true,
124
124
  handler: (e, tree) => {
125
125
  tree.setSelectedItems(tree.getItems().map((item) => item.getId()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@headless-tree/core",
3
- "version": "0.0.0-20250511190858",
3
+ "version": "0.0.0-20250511194715",
4
4
  "type": "module",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/esm/index.js",
@@ -50,7 +50,7 @@ export const expandAllFeature: FeatureImplementation = {
50
50
  handler: async (_, tree) => {
51
51
  const cancelToken = { current: false };
52
52
  const cancelHandler = (e: KeyboardEvent) => {
53
- if (e.key === "Escape") {
53
+ if (e.code === "Escape") {
54
54
  cancelToken.current = true;
55
55
  }
56
56
  };
@@ -63,7 +63,7 @@ export const expandAllFeature: FeatureImplementation = {
63
63
  },
64
64
 
65
65
  collapseSelected: {
66
- hotkey: "Control+Shift+-",
66
+ hotkey: "Control+Shift+Minus",
67
67
  handler: (_, tree) => {
68
68
  tree.getSelectedItems().forEach((item) => item.collapseAll());
69
69
  },
@@ -6,10 +6,13 @@ import {
6
6
  import { HotkeyConfig, HotkeysCoreDataRef } from "./types";
7
7
 
8
8
  const specialKeys: Record<string, RegExp> = {
9
- Letter: /^[a-z]$/,
10
- LetterOrNumber: /^[a-z0-9]$/,
11
- Plus: /^\+$/,
12
- Space: /^ $/,
9
+ // TODO:breaking deprecate auto-lowercase
10
+ letter: /^Key[A-Z]$/,
11
+ letterornumber: /^(Key[A-Z]|Digit[0-9])$/,
12
+ plus: /^(NumpadAdd|Plus)$/,
13
+ minus: /^(NumpadSubtract|Minus)$/,
14
+ control: /^(ControlLeft|ControlRight)$/,
15
+ shift: /^(ShiftLeft|ShiftRight)$/,
13
16
  };
14
17
 
15
18
  const testHotkeyMatch = (
@@ -17,12 +20,28 @@ const testHotkeyMatch = (
17
20
  tree: TreeInstance<any>,
18
21
  hotkey: HotkeyConfig<any>,
19
22
  ) => {
20
- const supposedKeys = hotkey.hotkey.toLowerCase().split("+");
21
- const doKeysMatch = supposedKeys.every((key) =>
22
- key in specialKeys
23
- ? [...pressedKeys].some((pressedKey) => specialKeys[key].test(pressedKey))
24
- : pressedKeys.has(key),
25
- );
23
+ const supposedKeys = hotkey.hotkey.toLowerCase().split("+"); // TODO:breaking deprecate auto-lowercase
24
+ const doKeysMatch = supposedKeys.every((key) => {
25
+ if (key in specialKeys) {
26
+ return [...pressedKeys].some((pressedKey) =>
27
+ specialKeys[key].test(pressedKey),
28
+ );
29
+ }
30
+
31
+ const pressedKeysLowerCase = [...pressedKeys] // TODO:breaking deprecate auto-lowercase
32
+ .map((k) => k.toLowerCase());
33
+
34
+ if (pressedKeysLowerCase.includes(key.toLowerCase())) {
35
+ return true;
36
+ }
37
+
38
+ if (pressedKeysLowerCase.includes(`key${key.toLowerCase()}`)) {
39
+ // TODO:breaking deprecate e.key character matching
40
+ return true;
41
+ }
42
+
43
+ return false;
44
+ });
26
45
  const isEnabled = !hotkey.isEnabled || hotkey.isEnabled(tree);
27
46
  const equalCounts = pressedKeys.size === supposedKeys.length;
28
47
  return doKeysMatch && isEnabled && equalCounts;
@@ -50,10 +69,9 @@ export const hotkeysCoreFeature: FeatureImplementation = {
50
69
  return;
51
70
  }
52
71
 
53
- const key = e.key.toLowerCase();
54
72
  data.current.pressedKeys ??= new Set();
55
- const newMatch = !data.current.pressedKeys.has(key);
56
- data.current.pressedKeys.add(key);
73
+ const newMatch = !data.current.pressedKeys.has(e.code);
74
+ data.current.pressedKeys.add(e.code);
57
75
 
58
76
  const hotkeyName = findHotkeyMatch(
59
77
  data.current.pressedKeys,
@@ -65,7 +83,7 @@ export const hotkeysCoreFeature: FeatureImplementation = {
65
83
  if (e.target instanceof HTMLInputElement) {
66
84
  // JS respects composite keydowns while input elements are focused, and
67
85
  // doesnt send the associated keyup events with the same key name
68
- data.current.pressedKeys.delete(key);
86
+ data.current.pressedKeys.delete(e.code);
69
87
  }
70
88
 
71
89
  if (!hotkeyName) return;
@@ -90,7 +108,7 @@ export const hotkeysCoreFeature: FeatureImplementation = {
90
108
 
91
109
  const keyup = (e: KeyboardEvent) => {
92
110
  data.current.pressedKeys ??= new Set();
93
- data.current.pressedKeys.delete(e.key.toLowerCase());
111
+ data.current.pressedKeys.delete(e.code);
94
112
  };
95
113
 
96
114
  const reset = () => {
@@ -187,7 +187,7 @@ export const keyboardDragAndDropFeature: FeatureImplementation = {
187
187
 
188
188
  hotkeys: {
189
189
  startDrag: {
190
- hotkey: "Control+Shift+D",
190
+ hotkey: "Control+Shift+KeyD",
191
191
  preventDefault: true,
192
192
  isEnabled: (tree) => !tree.getState().dnd,
193
193
  handler: (_, tree) => {
@@ -144,7 +144,7 @@ export const selectionFeature: FeatureImplementation = {
144
144
  },
145
145
  },
146
146
  selectAll: {
147
- hotkey: "Control+a",
147
+ hotkey: "Control+KeyA",
148
148
  preventDefault: true,
149
149
  handler: (e, tree) => {
150
150
  tree.setSelectedItems(tree.getItems().map((item) => item.getId()));