@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.
- package/CHANGELOG.md +40 -0
- package/dist/crons/index.d.ts +1 -0
- package/dist/crons/index.js +7 -1
- package/dist/crons/index.js.map +1 -1
- package/dist/crons/sweep-expired-sessions.d.ts +4 -0
- package/dist/crons/sweep-expired-sessions.js +27 -0
- package/dist/crons/sweep-expired-sessions.js.map +1 -0
- package/dist/db/models/userNotificationPreferences.model.js +17 -11
- package/dist/db/models/userNotificationPreferences.model.js.map +1 -1
- package/dist/db/tables/userNotifications.table.js +3 -0
- package/dist/db/tables/userNotifications.table.js.map +1 -1
- package/dist/pages/settings/users/profile-helpers.d.ts +2 -1
- package/dist/pages/settings/users/profile-helpers.js +9 -0
- package/dist/pages/settings/users/profile-helpers.js.map +1 -1
- package/dist/pages/settings/users/profile.js +2 -2
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/routes/auth/refresh.d.ts +1 -1
- package/dist/routes/auth/refresh.js +2 -1
- package/dist/routes/auth/refresh.js.map +1 -1
- package/dist/routes/auth/switch-tenant.d.ts +1 -1
- package/dist/routes/auth/switch-tenant.js +2 -1
- package/dist/routes/auth/switch-tenant.js.map +1 -1
- package/dist/routes/onboarding.js +1 -1
- package/dist/routes/onboarding.js.map +1 -1
- package/dist/test/antelope.test.js +1 -1
- package/dist/test/helpers/db.d.ts +1 -0
- package/dist/test/helpers/db.js +14 -0
- package/dist/test/helpers/db.js.map +1 -1
- package/dist/test/unit/db/email-normalization.test.d.ts +1 -0
- package/dist/test/unit/db/email-normalization.test.js +59 -0
- package/dist/test/unit/db/email-normalization.test.js.map +1 -0
- package/dist/test/unit/db/notification-feed-index.test.d.ts +1 -0
- package/dist/test/unit/db/notification-feed-index.test.js +35 -0
- package/dist/test/unit/db/notification-feed-index.test.js.map +1 -0
- package/dist/test/unit/db/notification-preferences.test.d.ts +1 -0
- package/dist/test/unit/db/notification-preferences.test.js +21 -0
- package/dist/test/unit/db/notification-preferences.test.js.map +1 -0
- package/dist/test/unit/db/session-refresh-token.test.d.ts +1 -0
- package/dist/test/unit/db/session-refresh-token.test.js +52 -0
- package/dist/test/unit/db/session-refresh-token.test.js.map +1 -0
- package/dist/test/unit/db/session-sweep.test.d.ts +1 -0
- package/dist/test/unit/db/session-sweep.test.js +44 -0
- package/dist/test/unit/db/session-sweep.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/password-policy.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/password-policy.test.js +43 -0
- package/dist/test/unit/interfaces/dms/password-policy.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-auth/refresh-token-rotation.test.js +74 -9
- package/dist/test/unit/interfaces/dms-auth/refresh-token-rotation.test.js.map +1 -1
- package/dist/test/unit/pages/settings/profile-email-change.test.d.ts +1 -0
- package/dist/test/unit/pages/settings/profile-email-change.test.js +50 -0
- package/dist/test/unit/pages/settings/profile-email-change.test.js.map +1 -0
- package/dist/validation/password.d.ts +0 -1
- package/dist/validation/password.js +6 -3
- package/dist/validation/password.js.map +1 -1
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +4 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +4 -0
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/tree/Tree.vue +10 -9
- package/frontend-vue/layers/dms-ui/app/composables/table-view/shortcuts/index.ts +17 -4
- package/frontend-vue/layers/dms-ui/app/composables/table-view/shortcuts/tableViewEscape.ts +22 -0
- package/frontend-vue/layers/dms-ui/app/composables/tree/shortcuts/index.ts +19 -4
- package/frontend-vue/layers/dms-ui/app/composables/tree/shortcuts/treeEscape.ts +21 -0
- package/frontend-vue/layers/dms-ui/app/utils/overlayFocus.ts +16 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +4 -2
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +4 -2
- package/frontend-vue/package.json +1 -1
- package/frontend-vue/pnpm-lock.yaml +5 -5
- package/frontend-vue/tests/table-view-escape-shortcut.test.ts +141 -0
- package/frontend-vue/tests/tree-escape-shortcut.test.ts +138 -0
- 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 {
|
|
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
|
-
|
|
40
|
-
|
|
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",
|
|
@@ -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
|
|
128
|
-
version: 0.2
|
|
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
|
|
204
|
-
resolution: {integrity: sha512-
|
|
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
|
|
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.
|
|
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.
|
|
38
|
-
"@antelopejs/interface-database-decorators": ">=0.1.
|
|
39
|
-
"@antelopejs/interface-dms": ">=0.2.
|
|
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",
|