@antelopejs/dms 0.4.6 → 0.4.8

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/crons/index.d.ts +1 -0
  3. package/dist/crons/index.js +7 -1
  4. package/dist/crons/index.js.map +1 -1
  5. package/dist/crons/sweep-expired-sessions.d.ts +4 -0
  6. package/dist/crons/sweep-expired-sessions.js +27 -0
  7. package/dist/crons/sweep-expired-sessions.js.map +1 -0
  8. package/dist/db/models/userNotificationPreferences.model.js +17 -11
  9. package/dist/db/models/userNotificationPreferences.model.js.map +1 -1
  10. package/dist/db/tables/userNotifications.table.js +3 -0
  11. package/dist/db/tables/userNotifications.table.js.map +1 -1
  12. package/dist/pages/settings/users/profile-helpers.d.ts +2 -1
  13. package/dist/pages/settings/users/profile-helpers.js +9 -0
  14. package/dist/pages/settings/users/profile-helpers.js.map +1 -1
  15. package/dist/pages/settings/users/profile.js +2 -2
  16. package/dist/pages/settings/users/profile.js.map +1 -1
  17. package/dist/routes/auth/refresh.d.ts +1 -1
  18. package/dist/routes/auth/refresh.js +2 -1
  19. package/dist/routes/auth/refresh.js.map +1 -1
  20. package/dist/routes/auth/switch-tenant.d.ts +1 -1
  21. package/dist/routes/auth/switch-tenant.js +2 -1
  22. package/dist/routes/auth/switch-tenant.js.map +1 -1
  23. package/dist/routes/onboarding.js +1 -1
  24. package/dist/routes/onboarding.js.map +1 -1
  25. package/dist/test/antelope.test.js +1 -1
  26. package/dist/test/helpers/db.d.ts +1 -0
  27. package/dist/test/helpers/db.js +14 -0
  28. package/dist/test/helpers/db.js.map +1 -1
  29. package/dist/test/unit/db/email-normalization.test.d.ts +1 -0
  30. package/dist/test/unit/db/email-normalization.test.js +59 -0
  31. package/dist/test/unit/db/email-normalization.test.js.map +1 -0
  32. package/dist/test/unit/db/notification-feed-index.test.d.ts +1 -0
  33. package/dist/test/unit/db/notification-feed-index.test.js +35 -0
  34. package/dist/test/unit/db/notification-feed-index.test.js.map +1 -0
  35. package/dist/test/unit/db/notification-preferences.test.d.ts +1 -0
  36. package/dist/test/unit/db/notification-preferences.test.js +21 -0
  37. package/dist/test/unit/db/notification-preferences.test.js.map +1 -0
  38. package/dist/test/unit/db/session-refresh-token.test.d.ts +1 -0
  39. package/dist/test/unit/db/session-refresh-token.test.js +52 -0
  40. package/dist/test/unit/db/session-refresh-token.test.js.map +1 -0
  41. package/dist/test/unit/db/session-sweep.test.d.ts +1 -0
  42. package/dist/test/unit/db/session-sweep.test.js +44 -0
  43. package/dist/test/unit/db/session-sweep.test.js.map +1 -0
  44. package/dist/test/unit/interfaces/dms/password-policy.test.d.ts +1 -0
  45. package/dist/test/unit/interfaces/dms/password-policy.test.js +43 -0
  46. package/dist/test/unit/interfaces/dms/password-policy.test.js.map +1 -0
  47. package/dist/test/unit/interfaces/dms-auth/refresh-token-rotation.test.js +74 -9
  48. package/dist/test/unit/interfaces/dms-auth/refresh-token-rotation.test.js.map +1 -1
  49. package/dist/test/unit/pages/settings/profile-email-change.test.d.ts +1 -0
  50. package/dist/test/unit/pages/settings/profile-email-change.test.js +50 -0
  51. package/dist/test/unit/pages/settings/profile-email-change.test.js.map +1 -0
  52. package/dist/validation/password.d.ts +0 -1
  53. package/dist/validation/password.js +6 -3
  54. package/dist/validation/password.js.map +1 -1
  55. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +4 -0
  56. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +4 -0
  57. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +2 -1
  58. package/frontend-vue/layers/dms-ui/app/components/tree/Tree.vue +10 -9
  59. package/frontend-vue/layers/dms-ui/app/composables/table-view/shortcuts/index.ts +17 -4
  60. package/frontend-vue/layers/dms-ui/app/composables/table-view/shortcuts/tableViewEscape.ts +22 -0
  61. package/frontend-vue/layers/dms-ui/app/composables/tree/shortcuts/index.ts +19 -4
  62. package/frontend-vue/layers/dms-ui/app/composables/tree/shortcuts/treeEscape.ts +21 -0
  63. package/frontend-vue/layers/dms-ui/app/utils/overlayFocus.ts +16 -0
  64. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +4 -2
  65. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +4 -2
  66. package/frontend-vue/package.json +1 -1
  67. package/frontend-vue/pnpm-lock.yaml +5 -5
  68. package/frontend-vue/tests/table-view-escape-shortcut.test.ts +141 -0
  69. package/frontend-vue/tests/tree-escape-shortcut.test.ts +138 -0
  70. package/package.json +4 -4
@@ -1,3 +1,4 @@
1
+ import { computed, type ComputedRef } from "vue";
1
2
  import {
2
3
  TREE_ARROW_DOWN_METADATA,
3
4
  createTreeArrowDownShortcut,
@@ -11,7 +12,11 @@ import { TREE_SPACE_METADATA, createTreeSpaceShortcut } from "./treeSpace";
11
12
  import { TREE_HOME_METADATA, createTreeHomeShortcut } from "./treeHome";
12
13
  import { TREE_END_METADATA, createTreeEndShortcut } from "./treeEnd";
13
14
  import { TREE_SHIFT_A_METADATA, createTreeShiftAShortcut } from "./treeShiftA";
14
- import { TREE_ESCAPE_METADATA, createTreeEscapeShortcut } from "./treeEscape";
15
+ import {
16
+ TREE_ESCAPE_METADATA,
17
+ createTreeEscapeShortcut,
18
+ isTreeEscapeActive,
19
+ } from "./treeEscape";
15
20
 
16
21
  export const TREE_SHORTCUTS_METADATA = [
17
22
  TREE_ARROW_DOWN_METADATA,
@@ -29,15 +34,19 @@ interface BuildTreeShortcutsParams {
29
34
  props: TreeProps;
30
35
  items: ComputedRef<TreeNode[]>;
31
36
  selected: Ref<TreeNode | TreeNode[] | undefined>;
37
+ activeElement: Ref<Element | null | undefined>;
32
38
  }
33
39
 
40
+ type TreeShortcuts = Record<string, (() => void) | undefined>;
41
+
34
42
  export function buildTreeShortcuts({
35
43
  navigateTree,
36
44
  props,
37
45
  items,
38
46
  selected,
39
- }: BuildTreeShortcutsParams): Record<string, () => void> {
40
- return {
47
+ activeElement,
48
+ }: BuildTreeShortcutsParams): ComputedRef<TreeShortcuts> {
49
+ const shortcuts = {
41
50
  arrowdown: createTreeArrowDownShortcut(navigateTree),
42
51
  arrowup: createTreeArrowUpShortcut(navigateTree),
43
52
  enter: createTreeEnterShortcut(navigateTree),
@@ -45,6 +54,12 @@ export function buildTreeShortcuts({
45
54
  home: createTreeHomeShortcut(navigateTree),
46
55
  end: createTreeEndShortcut(navigateTree),
47
56
  shift_a: createTreeShiftAShortcut(props, items, selected),
48
- escape: createTreeEscapeShortcut(props, selected),
49
57
  };
58
+ const escape = createTreeEscapeShortcut(props, selected);
59
+ // An unregistered key is left untouched by `defineShortcuts`, so Escape
60
+ // reaches overlays whenever the tree has nothing to clear.
61
+ return computed(() => ({
62
+ ...shortcuts,
63
+ escape: isTreeEscapeActive(selected, activeElement) ? escape : undefined,
64
+ }));
50
65
  }
@@ -1,9 +1,30 @@
1
+ import { isFocusInsideOverlay } from "../../../utils/overlayFocus";
2
+
1
3
  export const TREE_ESCAPE_METADATA: ShortcutMetadata = {
2
4
  key: ["$keyboard.escape"],
3
5
  descriptionKey: "$dms.shortcuts.tree.escape.description",
4
6
  component: "$dms.components.tree",
7
+ condition: {
8
+ descriptionKey: "$dms.shortcuts.tree.escape.condition",
9
+ },
5
10
  };
6
11
 
12
+ /**
13
+ * Nuxt UI's `defineShortcuts` calls `preventDefault()` on every key it
14
+ * handles, and reka-ui's DismissableLayer ignores a prevented Escape. The
15
+ * shortcut is therefore only registered when there is a selection to clear,
16
+ * and never while focus sits inside an overlay, so the overlay above the tree
17
+ * keeps closing on Escape.
18
+ */
19
+ export function isTreeEscapeActive(
20
+ selected: Ref<TreeNode | TreeNode[] | undefined>,
21
+ activeElement: Ref<Element | null | undefined>,
22
+ ): boolean {
23
+ if (isFocusInsideOverlay(activeElement.value)) return false;
24
+ const value = selected.value;
25
+ return Array.isArray(value) ? value.length > 0 : value != null;
26
+ }
27
+
7
28
  export function createTreeEscapeShortcut(
8
29
  props: TreeProps,
9
30
  selected: Ref<TreeNode | TreeNode[] | undefined>,
@@ -0,0 +1,16 @@
1
+ // Overlays reka-ui dismisses on Escape (modals, slideovers, popovers, menus,
2
+ // selects). They move focus inside themselves while open.
3
+ const OVERLAY_SELECTOR =
4
+ '[role="dialog"], [role="alertdialog"], [role="menu"], [role="listbox"]';
5
+
6
+ /**
7
+ * Nuxt UI's `defineShortcuts` calls `preventDefault()` on every key it
8
+ * handles, and reka-ui's DismissableLayer ignores a prevented Escape. A
9
+ * component-level Escape shortcut must therefore stay unregistered while
10
+ * focus sits inside an overlay, so the overlay keeps closing on Escape.
11
+ */
12
+ export function isFocusInsideOverlay(
13
+ activeElement: Element | null | undefined,
14
+ ): boolean {
15
+ return Boolean(activeElement?.closest(OVERLAY_SELECTOR));
16
+ }
@@ -292,7 +292,8 @@
292
292
  "description": "Refresh table data"
293
293
  },
294
294
  "escape": {
295
- "description": "Close search or deselect rows"
295
+ "description": "Close search or deselect rows",
296
+ "condition": "Only when a search or a row selection is active and no dialog is open"
296
297
  },
297
298
  "shift_n": {
298
299
  "description": "Create new row",
@@ -330,7 +331,8 @@
330
331
  "description": "Go to last node"
331
332
  },
332
333
  "escape": {
333
- "description": "Deselect all nodes"
334
+ "description": "Deselect all nodes",
335
+ "condition": "Only when nodes are selected and no dialog is open"
334
336
  },
335
337
  "shift_a": {
336
338
  "description": "Select all nodes recursively",
@@ -293,7 +293,8 @@
293
293
  "description": "Actualiser les données du tableau"
294
294
  },
295
295
  "escape": {
296
- "description": "Fermer la recherche ou désélectionner les lignes"
296
+ "description": "Fermer la recherche ou désélectionner les lignes",
297
+ "condition": "Uniquement quand une recherche ou une sélection est active et qu'aucune fenêtre n'est ouverte"
297
298
  },
298
299
  "shift_n": {
299
300
  "description": "Créer une nouvelle ligne",
@@ -331,7 +332,8 @@
331
332
  "description": "Aller au dernier nœud"
332
333
  },
333
334
  "escape": {
334
- "description": "Désélectionner tous les nœuds"
335
+ "description": "Désélectionner tous les nœuds",
336
+ "condition": "Uniquement quand des nœuds sont sélectionnés et qu'aucune fenêtre n'est ouverte"
335
337
  },
336
338
  "shift_a": {
337
339
  "description": "Sélectionner tous les nœuds récursivement",
@@ -59,7 +59,7 @@
59
59
  },
60
60
  "devDependencies": {
61
61
  "@antelopejs/core": ">=1.9.1 <2",
62
- "@antelopejs/dms-frontend": ">=0.2.8 <1.0.0",
62
+ "@antelopejs/dms-frontend": ">=0.3.2 <1.0.0",
63
63
  "@eslint/js": "^8.57.0",
64
64
  "@inertiajs/vue3": "^3.7.0",
65
65
  "@nuxt/ui": "4.10.0",
@@ -124,8 +124,8 @@ importers:
124
124
  specifier: '>=1.9.1 <2'
125
125
  version: 1.9.1(@types/node@24.8.1)
126
126
  '@antelopejs/dms-frontend':
127
- specifier: '>=0.2.8 <1.0.0'
128
- version: 0.2.8(@antelopejs/core@1.9.1(@types/node@24.8.1))
127
+ specifier: '>=0.3.2 <1.0.0'
128
+ version: 0.3.2(@antelopejs/core@1.9.1(@types/node@24.8.1))
129
129
  '@eslint/js':
130
130
  specifier: ^8.57.0
131
131
  version: 8.57.0
@@ -200,8 +200,8 @@ packages:
200
200
  resolution: {integrity: sha512-MPLJak0rPluV4lgsdsO5ba03sgTxIGSncajBcoaoppuppQmksdpaemojIHiVLJ4LiG0iiH21Roaed8XXy1XkYQ==}
201
201
  hasBin: true
202
202
 
203
- '@antelopejs/dms-frontend@0.2.8':
204
- resolution: {integrity: sha512-7qrlf2M3J26DZxGZVvM/3qjPDeDfoRMrE7HEPFxWav8Pe1QFct7LtgY7NmKup9XdrRyUw4nqckxvW8UZDSjpeg==}
203
+ '@antelopejs/dms-frontend@0.3.2':
204
+ resolution: {integrity: sha512-BNqLqW3UbfeOMnd4RkEynr/1mMWFhwB8dt1JbggaGte8/KrJQ3MQ5TOovnACMmrqAD7FxsMLBsNqjzGjX4O4ZA==}
205
205
  engines: {node: '>=20.12.0'}
206
206
  hasBin: true
207
207
  peerDependencies:
@@ -4331,7 +4331,7 @@ snapshots:
4331
4331
  transitivePeerDependencies:
4332
4332
  - '@types/node'
4333
4333
 
4334
- '@antelopejs/dms-frontend@0.2.8(@antelopejs/core@1.9.1(@types/node@24.8.1))':
4334
+ '@antelopejs/dms-frontend@0.3.2(@antelopejs/core@1.9.1(@types/node@24.8.1))':
4335
4335
  dependencies:
4336
4336
  boxen: 5.1.2
4337
4337
  chalk: 4.1.2
@@ -0,0 +1,141 @@
1
+ // @vitest-environment jsdom
2
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
3
+ import {
4
+ computed,
5
+ createApp,
6
+ defineComponent,
7
+ nextTick,
8
+ ref,
9
+ shallowRef,
10
+ type App,
11
+ type Ref,
12
+ type ShallowRef,
13
+ } from "vue";
14
+ import { useActiveElement } from "@vueuse/core";
15
+ import type { RowSelectionState } from "@tanstack/vue-table";
16
+ import { defineShortcuts } from "@nuxt/ui/runtime/composables/defineShortcuts.js";
17
+ import {
18
+ TABLE_VIEW_SHORTCUTS_METADATA,
19
+ buildTableViewShortcuts,
20
+ } from "../layers/dms-ui/app/composables/table-view/shortcuts";
21
+
22
+ let app: App;
23
+ let host: HTMLDivElement;
24
+ let globalFilter: ShallowRef<string | undefined>;
25
+ let rowSelect: Ref<RowSelectionState>;
26
+
27
+ function mountTableShortcuts() {
28
+ const Harness = defineComponent({
29
+ setup() {
30
+ defineShortcuts(
31
+ buildTableViewShortcuts({
32
+ refresh: () => {},
33
+ tableProps: computed(() => ({})),
34
+ newRow: () => {},
35
+ globalFilter,
36
+ rowSelect,
37
+ data: ref(null),
38
+ activeElement: useActiveElement(),
39
+ }),
40
+ );
41
+ return () => null;
42
+ },
43
+ });
44
+ app = createApp(Harness);
45
+ app.mount(host);
46
+ }
47
+
48
+ function pressEscape(target: EventTarget = document.body): KeyboardEvent {
49
+ const event = new KeyboardEvent("keydown", {
50
+ key: "Escape",
51
+ code: "Escape",
52
+ bubbles: true,
53
+ cancelable: true,
54
+ });
55
+ target.dispatchEvent(event);
56
+ return event;
57
+ }
58
+
59
+ beforeEach(() => {
60
+ host = document.createElement("div");
61
+ document.body.appendChild(host);
62
+ globalFilter = shallowRef<string | undefined>(undefined);
63
+ rowSelect = ref<RowSelectionState>({});
64
+ });
65
+
66
+ afterEach(() => {
67
+ app.unmount();
68
+ document.body.innerHTML = "";
69
+ });
70
+
71
+ describe("TableView Escape shortcut", () => {
72
+ it("leaves Escape to overlays when there is nothing to clear", async () => {
73
+ mountTableShortcuts();
74
+ await nextTick();
75
+
76
+ expect(pressEscape().defaultPrevented).toBe(false);
77
+ });
78
+
79
+ it("closes the search when a global filter is set", async () => {
80
+ globalFilter.value = "acme";
81
+ mountTableShortcuts();
82
+ await nextTick();
83
+
84
+ expect(pressEscape().defaultPrevented).toBe(true);
85
+ expect(globalFilter.value).toBeUndefined();
86
+ });
87
+
88
+ it("clears the row selection when no search is open", async () => {
89
+ rowSelect.value = { a: true, b: true };
90
+ mountTableShortcuts();
91
+ await nextTick();
92
+
93
+ expect(pressEscape().defaultPrevented).toBe(true);
94
+ expect(rowSelect.value).toEqual({});
95
+ });
96
+
97
+ it("closes the search before touching the selection", async () => {
98
+ globalFilter.value = "";
99
+ rowSelect.value = { a: true };
100
+ mountTableShortcuts();
101
+ await nextTick();
102
+
103
+ pressEscape();
104
+ expect(globalFilter.value).toBeUndefined();
105
+ expect(rowSelect.value).toEqual({ a: true });
106
+ });
107
+
108
+ it("stops handling Escape once the filter is cleared", async () => {
109
+ globalFilter.value = "acme";
110
+ mountTableShortcuts();
111
+ await nextTick();
112
+
113
+ pressEscape();
114
+ await nextTick();
115
+ expect(pressEscape().defaultPrevented).toBe(false);
116
+ });
117
+
118
+ it("lets an open dialog take Escape even with a filter set", async () => {
119
+ globalFilter.value = "acme";
120
+ mountTableShortcuts();
121
+ const dialog = document.createElement("div");
122
+ dialog.setAttribute("role", "dialog");
123
+ const button = document.createElement("button");
124
+ dialog.appendChild(button);
125
+ document.body.appendChild(dialog);
126
+ button.focus();
127
+ await nextTick();
128
+
129
+ expect(pressEscape(button).defaultPrevented).toBe(false);
130
+ expect(globalFilter.value).toBe("acme");
131
+ });
132
+
133
+ it("describes when the shortcut applies in the help list", () => {
134
+ const escape = TABLE_VIEW_SHORTCUTS_METADATA.find((metadata) =>
135
+ metadata.key.includes("$keyboard.escape"),
136
+ );
137
+ expect(escape?.condition?.descriptionKey).toBe(
138
+ "$dms.shortcuts.tableview.escape.condition",
139
+ );
140
+ });
141
+ });
@@ -0,0 +1,138 @@
1
+ // @vitest-environment jsdom
2
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
3
+ import {
4
+ computed,
5
+ createApp,
6
+ defineComponent,
7
+ nextTick,
8
+ ref,
9
+ type App,
10
+ type Ref,
11
+ } from "vue";
12
+ import { useActiveElement } from "@vueuse/core";
13
+ import { defineShortcuts } from "@nuxt/ui/runtime/composables/defineShortcuts.js";
14
+ import {
15
+ TREE_SHORTCUTS_METADATA,
16
+ buildTreeShortcuts,
17
+ } from "../layers/dms-ui/app/composables/tree/shortcuts";
18
+ import type {
19
+ TreeNode,
20
+ TreeProps,
21
+ } from "../layers/dms-ui/app/composables/tree/types";
22
+
23
+ let app: App;
24
+ let host: HTMLDivElement;
25
+ let selected: Ref<TreeNode | TreeNode[] | undefined>;
26
+
27
+ const nodeA = { label: "A", value: "a" } as TreeNode;
28
+ const nodeB = { label: "B", value: "b" } as TreeNode;
29
+
30
+ function mountTreeShortcuts(multiple = false) {
31
+ const Harness = defineComponent({
32
+ setup() {
33
+ defineShortcuts(
34
+ buildTreeShortcuts({
35
+ navigateTree: () => {},
36
+ props: { multiple } as TreeProps,
37
+ items: computed(() => [nodeA, nodeB]),
38
+ selected,
39
+ activeElement: useActiveElement(),
40
+ }),
41
+ );
42
+ return () => null;
43
+ },
44
+ });
45
+ app = createApp(Harness);
46
+ app.mount(host);
47
+ }
48
+
49
+ function pressEscape(target: EventTarget = document.body): KeyboardEvent {
50
+ const event = new KeyboardEvent("keydown", {
51
+ key: "Escape",
52
+ code: "Escape",
53
+ bubbles: true,
54
+ cancelable: true,
55
+ });
56
+ target.dispatchEvent(event);
57
+ return event;
58
+ }
59
+
60
+ beforeEach(() => {
61
+ host = document.createElement("div");
62
+ document.body.appendChild(host);
63
+ selected = ref<TreeNode | TreeNode[] | undefined>(undefined);
64
+ });
65
+
66
+ afterEach(() => {
67
+ app.unmount();
68
+ document.body.innerHTML = "";
69
+ });
70
+
71
+ describe("Tree Escape shortcut", () => {
72
+ it("leaves Escape to overlays when nothing is selected", async () => {
73
+ mountTreeShortcuts();
74
+ await nextTick();
75
+
76
+ expect(pressEscape().defaultPrevented).toBe(false);
77
+ });
78
+
79
+ it("leaves Escape to overlays when a multiple selection is empty", async () => {
80
+ selected.value = [];
81
+ mountTreeShortcuts(true);
82
+ await nextTick();
83
+
84
+ expect(pressEscape().defaultPrevented).toBe(false);
85
+ });
86
+
87
+ it("deselects the node in single selection mode", async () => {
88
+ selected.value = nodeA;
89
+ mountTreeShortcuts();
90
+ await nextTick();
91
+
92
+ expect(pressEscape().defaultPrevented).toBe(true);
93
+ expect(selected.value).toBeUndefined();
94
+ });
95
+
96
+ it("clears every node in multiple selection mode", async () => {
97
+ selected.value = [nodeA, nodeB];
98
+ mountTreeShortcuts(true);
99
+ await nextTick();
100
+
101
+ expect(pressEscape().defaultPrevented).toBe(true);
102
+ expect(selected.value).toEqual([]);
103
+ });
104
+
105
+ it("stops handling Escape once the selection is cleared", async () => {
106
+ selected.value = [nodeA];
107
+ mountTreeShortcuts(true);
108
+ await nextTick();
109
+
110
+ pressEscape();
111
+ await nextTick();
112
+ expect(pressEscape().defaultPrevented).toBe(false);
113
+ });
114
+
115
+ it("lets an open dialog take Escape even with a selection", async () => {
116
+ selected.value = nodeA;
117
+ mountTreeShortcuts();
118
+ const dialog = document.createElement("div");
119
+ dialog.setAttribute("role", "dialog");
120
+ const button = document.createElement("button");
121
+ dialog.appendChild(button);
122
+ document.body.appendChild(dialog);
123
+ button.focus();
124
+ await nextTick();
125
+
126
+ expect(pressEscape(button).defaultPrevented).toBe(false);
127
+ expect(selected.value).toStrictEqual(nodeA);
128
+ });
129
+
130
+ it("describes when the shortcut applies in the help list", () => {
131
+ const escape = TREE_SHORTCUTS_METADATA.find((metadata) =>
132
+ metadata.key.includes("$keyboard.escape"),
133
+ );
134
+ expect(escape?.condition?.descriptionKey).toBe(
135
+ "$dms.shortcuts.tree.escape.condition",
136
+ );
137
+ });
138
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/AntelopeJS/dms.git"
@@ -34,9 +34,9 @@
34
34
  "@antelopejs/interface-api-util": ">=0.1.2 <1.0.0",
35
35
  "@antelopejs/interface-core": ">=0.1.1 <1.0.0",
36
36
  "@antelopejs/interface-data-api": ">=0.1.9 <1.0.0",
37
- "@antelopejs/interface-database": ">=0.1.7 <1.0.0",
38
- "@antelopejs/interface-database-decorators": ">=0.1.6 <1.0.0",
39
- "@antelopejs/interface-dms": ">=0.2.5 <1.0.0",
37
+ "@antelopejs/interface-database": ">=0.1.8 <1.0.0",
38
+ "@antelopejs/interface-database-decorators": ">=0.1.7 <1.0.0",
39
+ "@antelopejs/interface-dms": ">=0.2.6 <1.0.0",
40
40
  "@antelopejs/interface-email": ">=0.0.5 <1.0.0",
41
41
  "@antelopejs/interface-file-storage": ">=0.1.3 <1.0.0",
42
42
  "archiver": "^7.0.1",