@antelopejs/dms 0.7.3 → 0.7.5
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 +28 -0
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js +16 -0
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js.map +1 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +15 -9
- package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +14 -40
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +49 -9
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useReorderHandles.ts +93 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableReorder.ts +8 -7
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +8 -2
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +51 -0
- package/frontend-vue/tests/table-actions-search-label.test.ts +22 -1
- package/frontend-vue/tests/table-cell-wrap.test.ts +3 -1
- package/frontend-vue/tests/table-row-hover.test.ts +3 -1
- package/frontend-vue/tests/table-scroll-area.test.ts +3 -1
- package/frontend-vue/tests/table-view-card-props.test.ts +8 -2
- package/frontend-vue/tests/table-view-cards-reorder.test.ts +250 -0
- package/frontend-vue/tests/table-view-reorder.test.ts +2 -2
- package/package.json +2 -2
- package/skills/dms/SKILL.md +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.7.5
|
|
4
|
+
|
|
5
|
+
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.7.4...v0.7.5)
|
|
6
|
+
|
|
7
|
+
### 🚀 Enhancements
|
|
8
|
+
|
|
9
|
+
- **table-view:** Reorder the cards of the cards display ([#194](https://github.com/AntelopeJS/dms/pull/194))
|
|
10
|
+
|
|
11
|
+
### 🏡 Chore
|
|
12
|
+
|
|
13
|
+
- **release:** @antelopejs/interface-dms v0.5.2 ([83da6c1](https://github.com/AntelopeJS/dms/commit/83da6c1))
|
|
14
|
+
|
|
15
|
+
### ❤️ Contributors
|
|
16
|
+
|
|
17
|
+
- Antony Rizzitelli <rizzitelli.antony@pm.me>
|
|
18
|
+
|
|
19
|
+
## v0.7.4
|
|
20
|
+
|
|
21
|
+
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.7.3...v0.7.4)
|
|
22
|
+
|
|
23
|
+
### 🩹 Fixes
|
|
24
|
+
|
|
25
|
+
- **table-view:** Open the toolbar search from its button again ([#193](https://github.com/AntelopeJS/dms/pull/193))
|
|
26
|
+
|
|
27
|
+
### ❤️ Contributors
|
|
28
|
+
|
|
29
|
+
- Antony Rizzitelli <rizzitelli.antony@pm.me>
|
|
30
|
+
|
|
3
31
|
## v0.7.3
|
|
4
32
|
|
|
5
33
|
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.7.2...v0.7.3)
|
|
@@ -16,6 +16,7 @@ const interface_database_decorators_1 = require("@antelopejs/interface-database-
|
|
|
16
16
|
const chai_1 = require("chai");
|
|
17
17
|
const constants_1 = require("@antelopejs/interface-dms/constants");
|
|
18
18
|
const default_types_1 = require("@antelopejs/interface-dms/base/data-types/default-types");
|
|
19
|
+
const custom_1 = require("@antelopejs/interface-dms/base/custom");
|
|
19
20
|
const table_view_1 = require("@antelopejs/interface-dms/base/table-view");
|
|
20
21
|
const TABLE = "reorder-features";
|
|
21
22
|
let Feature = class Feature extends interface_database_decorators_1.Table {
|
|
@@ -164,6 +165,21 @@ describe("[unit] interfaces/dms-base — table view sources and reorder", () =>
|
|
|
164
165
|
(0, chai_1.expect)(options.reorder).to.deep.equal({ field: "position" });
|
|
165
166
|
(0, chai_1.expect)(options.pagination).to.equal("infinite");
|
|
166
167
|
});
|
|
168
|
+
it("serves the reorder field beside a cards display drawn by a card component", () => {
|
|
169
|
+
const component = (0, custom_1.CustomComponent)("FeatureCard");
|
|
170
|
+
const options = optionsOf({
|
|
171
|
+
labelKey: "name",
|
|
172
|
+
reorder: { field: "position" },
|
|
173
|
+
displays: [{ id: table_view_1.CARDS_DISPLAY_ID }],
|
|
174
|
+
defaultDisplay: table_view_1.CARDS_DISPLAY_ID,
|
|
175
|
+
card: { component },
|
|
176
|
+
});
|
|
177
|
+
(0, chai_1.expect)(options.reorder).to.deep.equal({ field: "position" });
|
|
178
|
+
const cards = options.displays?.find((display) => display.id === table_view_1.CARDS_DISPLAY_ID);
|
|
179
|
+
(0, chai_1.expect)(cards?.options?.card).to.deep.include({
|
|
180
|
+
component: component.serializeSync(),
|
|
181
|
+
});
|
|
182
|
+
});
|
|
167
183
|
it("refuses a reorder field the rows cannot be sorted on nor saved by", () => {
|
|
168
184
|
(0, chai_1.expect)(() => optionsOf({ reorder: { field: "name" } })).to.throw(/must be a NumberType column/);
|
|
169
185
|
(0, chai_1.expect)(() => optionsOf({ reorder: { field: "votes" } })).to.throw(/must be @Sortable\(\)/);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table-view-source-reorder.test.js","sourceRoot":"","sources":["../../../../../src/test/unit/interfaces/dms-base/table-view-source-reorder.test.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,6DAA4E;AAC5E,uEAGwC;AACxC,sEAMiD;AACjD,6FAMmD;AACnD,+BAA8B;AAC9B,mEAAuE;AACvE,2FAA2F;AAC3F,
|
|
1
|
+
{"version":3,"file":"table-view-source-reorder.test.js","sourceRoot":"","sources":["../../../../../src/test/unit/interfaces/dms-base/table-view-source-reorder.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts" generic="T extends Data">
|
|
2
|
-
import { injectLocal,
|
|
2
|
+
import { injectLocal, useFocusWithin } from "@vueuse/core";
|
|
3
3
|
import { tv } from "tailwind-variants";
|
|
4
|
-
import type
|
|
4
|
+
import { nextTick, type ShallowRef } from "vue";
|
|
5
5
|
import type { DmsAppConfig } from "#dms-core/shared/types/app-config";
|
|
6
6
|
|
|
7
7
|
import TableMenu from "./Menu.vue";
|
|
@@ -162,7 +162,6 @@ const searchActive = ref(false);
|
|
|
162
162
|
const searchInputRef = ref<HTMLInputElement>();
|
|
163
163
|
const searchSectionRef = ref<HTMLElement>();
|
|
164
164
|
|
|
165
|
-
const { focused: inputFocus } = useFocus(searchInputRef);
|
|
166
165
|
const { focused: searchAreaFocused } = useFocusWithin(searchSectionRef);
|
|
167
166
|
|
|
168
167
|
// A search set from outside (a view opened) shows in its field.
|
|
@@ -183,16 +182,17 @@ tableSharedData.onFiltersCleared(() => {
|
|
|
183
182
|
if (!searchAreaFocused.value) searchActive.value = false;
|
|
184
183
|
});
|
|
185
184
|
|
|
186
|
-
|
|
185
|
+
// Opening the search unmounts the trigger button that held the focus: the
|
|
186
|
+
// area reads as left for a moment, until the field takes the focus. Only an
|
|
187
|
+
// area still left once the field rendered folds the search back.
|
|
188
|
+
watch(searchAreaFocused, async (isFocused) => {
|
|
187
189
|
if (isFocused || !searchActive.value) return;
|
|
190
|
+
await nextTick();
|
|
191
|
+
if (searchAreaFocused.value || !searchActive.value) return;
|
|
188
192
|
if (tableSharedData?.globalFilterState.value) return;
|
|
189
193
|
searchActive.value = false;
|
|
190
194
|
});
|
|
191
195
|
|
|
192
|
-
const toggleSearch = () => {
|
|
193
|
-
searchActive.value = !searchActive.value;
|
|
194
|
-
inputFocus.value = !inputFocus.value;
|
|
195
|
-
};
|
|
196
196
|
|
|
197
197
|
const searchFieldRef = ref<{ $el: HTMLElement }>();
|
|
198
198
|
|
|
@@ -205,6 +205,12 @@ const focusSearchBar = () => {
|
|
|
205
205
|
?.focus();
|
|
206
206
|
};
|
|
207
207
|
|
|
208
|
+
const openSearch = async () => {
|
|
209
|
+
searchActive.value = true;
|
|
210
|
+
await nextTick();
|
|
211
|
+
focusSearchBar();
|
|
212
|
+
};
|
|
213
|
+
|
|
208
214
|
const searchPlaceholderText = computed(() =>
|
|
209
215
|
props.searchPlaceholder
|
|
210
216
|
? processI18n(props.searchPlaceholder)
|
|
@@ -426,7 +432,7 @@ const uiTableActions = computed(() => uiTableActionsVariant());
|
|
|
426
432
|
square
|
|
427
433
|
:aria-label="t('dms.table.search_open')"
|
|
428
434
|
:class="uiTableActions.trigger()"
|
|
429
|
-
@click="
|
|
435
|
+
@click="openSearch"
|
|
430
436
|
/>
|
|
431
437
|
|
|
432
438
|
<div :class="uiTableActions.searchContainer({ searchActive })">
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
import type {
|
|
30
30
|
CustomButton,
|
|
31
31
|
TableViewDisplayCapabilities,
|
|
32
|
+
TableViewDisplayReorder,
|
|
32
33
|
TableViewEmptyStatesConfig,
|
|
33
34
|
} from "../../../composables/table-view/types";
|
|
34
35
|
import type {
|
|
@@ -44,6 +45,10 @@ import type { ResolvedQuickFilter } from "../../composables/table-view/utils/qui
|
|
|
44
45
|
import type { ClearableTableFilters } from "../../composables/table/utils/clearTableFilters";
|
|
45
46
|
import type { EventHookOn } from "@vueuse/core";
|
|
46
47
|
import type { VNodeChild } from "vue";
|
|
48
|
+
import {
|
|
49
|
+
REORDER_HANDLE_CLASS,
|
|
50
|
+
useReorderHandles,
|
|
51
|
+
} from "../../composables/table-view/useReorderHandles";
|
|
47
52
|
|
|
48
53
|
export interface Data {
|
|
49
54
|
[key: string]: unknown;
|
|
@@ -82,12 +87,7 @@ export interface TableAccumulation {
|
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
/** Rows ordered by hand (backend `reorder`): a handle moves a row. */
|
|
85
|
-
export
|
|
86
|
-
/** Off while a search, a filter or a tab narrows the rows. */
|
|
87
|
-
enabled: boolean;
|
|
88
|
-
/** Moves the row at `from` to `to`, within the page. */
|
|
89
|
-
move: (from: number, to: number) => void;
|
|
90
|
-
}
|
|
90
|
+
export type TableReorder = TableViewDisplayReorder;
|
|
91
91
|
|
|
92
92
|
/** Where a table draws its views (see `TableProps.viewsPlacement`). */
|
|
93
93
|
export type TableViewsPlacement = "band" | "header";
|
|
@@ -531,8 +531,7 @@ const theme = tv({
|
|
|
531
531
|
handleHeadCell: `${HEADER_MATCH_BG} border-b-default w-8 border-b p-0`,
|
|
532
532
|
handleCell:
|
|
533
533
|
"border-b-muted w-8 border-b ps-2 pe-0 in-[tr:last-child]:border-b-0",
|
|
534
|
-
handle:
|
|
535
|
-
"inline-flex size-6 cursor-grab items-center justify-center rounded text-dimmed hover:text-highlighted focus-visible:outline-2 focus-visible:outline-(--dms-accent-line) disabled:cursor-not-allowed disabled:opacity-40 [&>svg]:size-4",
|
|
534
|
+
handle: REORDER_HANDLE_CLASS,
|
|
536
535
|
},
|
|
537
536
|
variants: {
|
|
538
537
|
cellWrap: {
|
|
@@ -809,23 +808,10 @@ const leadingCellCount = computed(
|
|
|
809
808
|
() => (hasPresenceRail.value ? 1 : 0) + (props.reorder ? 1 : 0),
|
|
810
809
|
);
|
|
811
810
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
const from = draggedRowIndex.value;
|
|
817
|
-
draggedRowIndex.value = undefined;
|
|
818
|
-
if (from !== undefined && props.reorder?.enabled) {
|
|
819
|
-
props.reorder.move(from, index);
|
|
820
|
-
}
|
|
821
|
-
};
|
|
822
|
-
const stepRow = (index: number, step: number) => {
|
|
823
|
-
const target = index + step;
|
|
824
|
-
const count = table.getRowModel().rows.length;
|
|
825
|
-
if (props.reorder?.enabled && target >= 0 && target < count) {
|
|
826
|
-
props.reorder.move(index, target);
|
|
827
|
-
}
|
|
828
|
-
};
|
|
811
|
+
const reorderHandles = useReorderHandles({
|
|
812
|
+
reorder: () => props.reorder,
|
|
813
|
+
count: () => table.getRowModel().rows.length,
|
|
814
|
+
});
|
|
829
815
|
|
|
830
816
|
// Pinned columns stick only while they cover at most 60% of the visible scroll
|
|
831
817
|
// area: on a phone, labelled row actions or a wide pinned set would otherwise
|
|
@@ -1497,8 +1483,8 @@ defineShortcuts({
|
|
|
1497
1483
|
@dblclick="handleRowDoubleClick(row.original)"
|
|
1498
1484
|
@mouseenter="handleRowHover(row)"
|
|
1499
1485
|
@mouseleave="handleRowLeave"
|
|
1500
|
-
@dragover="
|
|
1501
|
-
@drop="
|
|
1486
|
+
@dragover="reorderHandles.allowDrop"
|
|
1487
|
+
@drop="reorderHandles.drop(row.index)"
|
|
1502
1488
|
>
|
|
1503
1489
|
<td
|
|
1504
1490
|
v-if="hasPresenceRail"
|
|
@@ -1515,20 +1501,8 @@ defineShortcuts({
|
|
|
1515
1501
|
/>
|
|
1516
1502
|
<td v-if="reorder" :class="uiTable.handleCell()">
|
|
1517
1503
|
<button
|
|
1518
|
-
|
|
1519
|
-
:draggable="reorder.enabled"
|
|
1520
|
-
:disabled="!reorder.enabled"
|
|
1521
|
-
:aria-label="t('dms.table.reorder.move')"
|
|
1522
|
-
:title="
|
|
1523
|
-
reorder.enabled
|
|
1524
|
-
? t('dms.table.reorder.move')
|
|
1525
|
-
: t('dms.table.reorder.disabled')
|
|
1526
|
-
"
|
|
1504
|
+
v-bind="reorderHandles.handleFor(row.index)"
|
|
1527
1505
|
:class="uiTable.handle()"
|
|
1528
|
-
@dragstart="draggedRowIndex = row.index"
|
|
1529
|
-
@dragend="draggedRowIndex = undefined"
|
|
1530
|
-
@keydown.up.prevent="stepRow(row.index, -1)"
|
|
1531
|
-
@keydown.down.prevent="stepRow(row.index, 1)"
|
|
1532
1506
|
>
|
|
1533
1507
|
<UIcon name="i-ph-dots-six-vertical" />
|
|
1534
1508
|
</button>
|
|
@@ -6,12 +6,17 @@ import CardGridSkeleton from "./CardGridSkeleton.vue";
|
|
|
6
6
|
import type { TableViewColumn } from "../../../composables/table-view/types/column";
|
|
7
7
|
import type {
|
|
8
8
|
TableViewCardConfig,
|
|
9
|
+
TableViewCardReorder,
|
|
9
10
|
TableViewDisplayContext,
|
|
10
11
|
} from "../../../composables/table-view/types/display";
|
|
11
12
|
import {
|
|
12
13
|
buildCardProps,
|
|
13
14
|
cardFieldColumns,
|
|
14
15
|
} from "../../composables/table-view/utils/card";
|
|
16
|
+
import {
|
|
17
|
+
REORDER_HANDLE_CLASS,
|
|
18
|
+
useReorderHandles,
|
|
19
|
+
} from "../../composables/table-view/useReorderHandles";
|
|
15
20
|
|
|
16
21
|
interface CardsDisplayProps {
|
|
17
22
|
context: TableViewDisplayContext<T>;
|
|
@@ -85,6 +90,22 @@ const FieldValue = (fieldProps: { column: TableViewColumn; item: T }) => {
|
|
|
85
90
|
};
|
|
86
91
|
|
|
87
92
|
const openItem = (item: T) => props.context.actions.open(item);
|
|
93
|
+
|
|
94
|
+
// A hand-ordered table (backend `reorder`): each card carries a move handle,
|
|
95
|
+
// and is where another card dragged by its handle drops.
|
|
96
|
+
const { draggedIndex, handleFor, drop, allowDrop } = useReorderHandles({
|
|
97
|
+
reorder: () => props.context.reorder,
|
|
98
|
+
count: () => props.context.items.length,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const cardReorder = (index: number): TableViewCardReorder | undefined =>
|
|
102
|
+
props.context.reorder
|
|
103
|
+
? {
|
|
104
|
+
enabled: props.context.reorder.enabled,
|
|
105
|
+
dragging: draggedIndex.value === index,
|
|
106
|
+
handle: handleFor(index),
|
|
107
|
+
}
|
|
108
|
+
: undefined;
|
|
88
109
|
</script>
|
|
89
110
|
|
|
90
111
|
<template>
|
|
@@ -107,30 +128,49 @@ const openItem = (item: T) => props.context.actions.open(item);
|
|
|
107
128
|
class="grid grid-cols-[repeat(auto-fill,minmax(min(230px,100%),1fr))] gap-3 px-[18px] pt-4 pb-[18px]"
|
|
108
129
|
>
|
|
109
130
|
<template v-if="cardComponent">
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
<!-- A wrapper out of the grid's layout, catching the drop on the
|
|
132
|
+
card whatever its root element. -->
|
|
133
|
+
<div
|
|
134
|
+
v-for="(item, index) in props.context.items"
|
|
113
135
|
:key="rowId(item)"
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
136
|
+
class="contents"
|
|
137
|
+
@dragover="allowDrop"
|
|
138
|
+
@drop="drop(index)"
|
|
139
|
+
>
|
|
140
|
+
<component
|
|
141
|
+
:is="cardComponent"
|
|
142
|
+
v-bind="{
|
|
143
|
+
...(card?.component?.options ?? {}),
|
|
144
|
+
...buildCardProps(item, props.context),
|
|
145
|
+
reorder: cardReorder(index),
|
|
146
|
+
}"
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
119
149
|
</template>
|
|
120
150
|
<template v-else>
|
|
121
151
|
<article
|
|
122
|
-
v-for="item in props.context.items"
|
|
152
|
+
v-for="(item, index) in props.context.items"
|
|
123
153
|
:key="rowId(item)"
|
|
124
154
|
class="group border-default hover:border-primary/35 @container cursor-pointer rounded-[10px] border bg-(--ui-bg) p-3.5 text-[12.5px] transition-colors focus-visible:outline-2 focus-visible:outline-(--dms-accent-line)"
|
|
125
155
|
:class="{
|
|
126
156
|
'border-primary ring-primary ring-1':
|
|
127
157
|
props.context.selection.isSelected(rowId(item)),
|
|
158
|
+
'opacity-50': draggedIndex === index,
|
|
128
159
|
}"
|
|
129
160
|
tabindex="0"
|
|
130
161
|
@click="openItem(item)"
|
|
131
162
|
@keydown.enter.self="openItem(item)"
|
|
163
|
+
@dragover="allowDrop"
|
|
164
|
+
@drop="drop(index)"
|
|
132
165
|
>
|
|
133
166
|
<header class="flex items-center gap-2.5">
|
|
167
|
+
<button
|
|
168
|
+
v-if="props.context.reorder"
|
|
169
|
+
v-bind="handleFor(index)"
|
|
170
|
+
:class="['-ms-1.5', REORDER_HANDLE_CLASS]"
|
|
171
|
+
>
|
|
172
|
+
<UIcon name="i-ph-dots-six-vertical" />
|
|
173
|
+
</button>
|
|
134
174
|
<span
|
|
135
175
|
class="bg-accented text-default grid size-7 shrink-0 place-items-center rounded-[7px] font-mono text-[10.5px] font-bold"
|
|
136
176
|
>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { nextTick, ref } from "vue";
|
|
2
|
+
import type {
|
|
3
|
+
TableViewDisplayReorder,
|
|
4
|
+
TableViewReorderHandle,
|
|
5
|
+
} from "../../../composables/table-view/types/display";
|
|
6
|
+
|
|
7
|
+
// One place back or forward: a row of the grid steps up and down, a card of
|
|
8
|
+
// the cards grid also left and right.
|
|
9
|
+
const STEP_BY_KEY: Record<string, number> = {
|
|
10
|
+
ArrowUp: -1,
|
|
11
|
+
ArrowLeft: -1,
|
|
12
|
+
ArrowDown: 1,
|
|
13
|
+
ArrowRight: 1,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const DRAG_EFFECT = "move";
|
|
17
|
+
|
|
18
|
+
/** The look of a move handle, on a grid row and on a card. */
|
|
19
|
+
export const REORDER_HANDLE_CLASS =
|
|
20
|
+
"inline-flex size-6 shrink-0 cursor-grab items-center justify-center rounded text-dimmed hover:text-highlighted focus-visible:outline-2 focus-visible:outline-(--dms-accent-line) disabled:cursor-not-allowed disabled:opacity-40 [&>svg]:size-4";
|
|
21
|
+
|
|
22
|
+
export interface ReorderHandlesOptions {
|
|
23
|
+
reorder: () => TableViewDisplayReorder | undefined;
|
|
24
|
+
/** How many rows the display draws: a step past either end does nothing. */
|
|
25
|
+
count: () => number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The move handles of a hand-ordered display, the grid's and the cards'
|
|
30
|
+
* alike: the row dragged by its handle is dropped on another row, and the
|
|
31
|
+
* arrow keys on a handle move its row by one, keeping the focus on it.
|
|
32
|
+
*/
|
|
33
|
+
export function useReorderHandles(options: ReorderHandlesOptions) {
|
|
34
|
+
const { t } = useI18n();
|
|
35
|
+
const draggedIndex = ref<number | undefined>();
|
|
36
|
+
|
|
37
|
+
const isEnabled = () => !!options.reorder()?.enabled;
|
|
38
|
+
|
|
39
|
+
const drop = (index: number) => {
|
|
40
|
+
const from = draggedIndex.value;
|
|
41
|
+
draggedIndex.value = undefined;
|
|
42
|
+
if (from !== undefined && isEnabled()) options.reorder()!.move(from, index);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const allowDrop = (event: DragEvent) => {
|
|
46
|
+
if (isEnabled()) event.preventDefault();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const step = (index: number, delta: number) => {
|
|
50
|
+
const target = index + delta;
|
|
51
|
+
if (!isEnabled() || target < 0 || target >= options.count()) return false;
|
|
52
|
+
options.reorder()!.move(index, target);
|
|
53
|
+
return true;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const startDrag = (index: number, event: DragEvent) => {
|
|
57
|
+
draggedIndex.value = index;
|
|
58
|
+
// Firefox starts a drag only once it carries data.
|
|
59
|
+
event.dataTransfer?.setData("text/plain", String(index));
|
|
60
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = DRAG_EFFECT;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// The handle moves with its row (rows are keyed by id), and the browser
|
|
64
|
+
// drops the focus of an element it moves: give it back.
|
|
65
|
+
const stepByKey = (index: number, event: KeyboardEvent) => {
|
|
66
|
+
const delta = STEP_BY_KEY[event.key];
|
|
67
|
+
if (delta === undefined) return;
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
const handle = event.currentTarget as HTMLElement | null;
|
|
70
|
+
if (step(index, delta)) void nextTick(() => handle?.focus());
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const handleFor = (index: number): TableViewReorderHandle => {
|
|
74
|
+
const enabled = isEnabled();
|
|
75
|
+
return {
|
|
76
|
+
type: "button",
|
|
77
|
+
draggable: enabled,
|
|
78
|
+
disabled: !enabled,
|
|
79
|
+
"aria-label": t("dms.table.reorder.move"),
|
|
80
|
+
title: enabled
|
|
81
|
+
? t("dms.table.reorder.move")
|
|
82
|
+
: t("dms.table.reorder.disabled"),
|
|
83
|
+
onClick: (event) => event.stopPropagation(),
|
|
84
|
+
onDragstart: (event) => startDrag(index, event),
|
|
85
|
+
onDragend: () => {
|
|
86
|
+
draggedIndex.value = undefined;
|
|
87
|
+
},
|
|
88
|
+
onKeydown: (event) => stepByKey(index, event),
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return { draggedIndex, handleFor, drop, allowDrop };
|
|
93
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { get } from "@nuxt/ui/runtime/utils/index.js";
|
|
2
2
|
import type { ComputedRef, Ref } from "vue";
|
|
3
3
|
import type {
|
|
4
|
+
TableViewDisplayReorder,
|
|
4
5
|
TableViewListResponse,
|
|
5
6
|
TableViewReorderConfig,
|
|
6
7
|
} from "../../../composables/table-view/types";
|
|
7
|
-
import type { TableReorder } from "../../components/table/Table.vue";
|
|
8
8
|
import { moveRow, reorderEdits } from "./utils/reorder";
|
|
9
9
|
|
|
10
10
|
export interface TableReorderOptions<T> {
|
|
@@ -18,8 +18,8 @@ export interface TableReorderOptions<T> {
|
|
|
18
18
|
canEdit: ComputedRef<boolean>;
|
|
19
19
|
/** A search, a filter or a tab narrows the rows. */
|
|
20
20
|
isNarrowed: ComputedRef<boolean>;
|
|
21
|
-
/** The
|
|
22
|
-
|
|
21
|
+
/** The display shown draws the rows in their order: the grid, the cards. */
|
|
22
|
+
isOrderedDisplay: ComputedRef<boolean>;
|
|
23
23
|
/** Lists the rows again, after a move failed. */
|
|
24
24
|
refresh: () => Promise<void> | void;
|
|
25
25
|
onError: (error: unknown) => void;
|
|
@@ -28,8 +28,9 @@ export interface TableReorderOptions<T> {
|
|
|
28
28
|
/**
|
|
29
29
|
* Rows ordered by hand: a move updates the page at once and saves, by a
|
|
30
30
|
* partial edit, only the rows whose position changed. Moving is off while
|
|
31
|
-
* the rows are narrowed (the handle says how to turn it back on);
|
|
32
|
-
*
|
|
31
|
+
* the rows are narrowed (the handle says how to turn it back on); on a
|
|
32
|
+
* display that does not draw the rows in their order (the board, the
|
|
33
|
+
* groups), or for a caller who may not edit, there is no handle at all.
|
|
33
34
|
*/
|
|
34
35
|
export function useTableReorder<T>(options: TableReorderOptions<T>) {
|
|
35
36
|
const { reorder, data, rowIdKey } = options;
|
|
@@ -66,8 +67,8 @@ export function useTableReorder<T>(options: TableReorderOptions<T>) {
|
|
|
66
67
|
}
|
|
67
68
|
};
|
|
68
69
|
|
|
69
|
-
const state = computed<
|
|
70
|
-
if (!reorder || !options.
|
|
70
|
+
const state = computed<TableViewDisplayReorder | undefined>(() => {
|
|
71
|
+
if (!reorder || !options.isOrderedDisplay.value || !options.canEdit.value) {
|
|
71
72
|
return undefined;
|
|
72
73
|
}
|
|
73
74
|
return { enabled: !options.isNarrowed.value, move };
|
|
@@ -37,6 +37,7 @@ import type {
|
|
|
37
37
|
} from "../../composables/table-view/types";
|
|
38
38
|
import {
|
|
39
39
|
TABLE_DISPLAY_ID,
|
|
40
|
+
CARDS_DISPLAY_ID,
|
|
40
41
|
KANBAN_DISPLAY_ID,
|
|
41
42
|
GROUPED_DISPLAY_ID,
|
|
42
43
|
TableViewEvents,
|
|
@@ -1019,7 +1020,9 @@ const accumulation = computed<TableAccumulation | undefined>(() =>
|
|
|
1019
1020
|
: undefined,
|
|
1020
1021
|
);
|
|
1021
1022
|
|
|
1022
|
-
// Rows ordered by hand: moving is off while anything narrows the rows
|
|
1023
|
+
// Rows ordered by hand: moving is off while anything narrows the rows, and
|
|
1024
|
+
// only the displays drawing the rows in their order move them.
|
|
1025
|
+
const ORDERED_DISPLAY_IDS = new Set([TABLE_DISPLAY_ID, CARDS_DISPLAY_ID]);
|
|
1023
1026
|
const { reorderState } = useTableReorder<T>({
|
|
1024
1027
|
reorder: reorderConfig,
|
|
1025
1028
|
data,
|
|
@@ -1033,7 +1036,9 @@ const { reorderState } = useTableReorder<T>({
|
|
|
1033
1036
|
!!effectiveGlobalFilter.value ||
|
|
1034
1037
|
activeTabFilters.value.length > 0,
|
|
1035
1038
|
),
|
|
1036
|
-
|
|
1039
|
+
isOrderedDisplay: computed(() =>
|
|
1040
|
+
ORDERED_DISPLAY_IDS.has(activeDisplayId.value),
|
|
1041
|
+
),
|
|
1037
1042
|
refresh: () => refresh(),
|
|
1038
1043
|
onError: (failure) =>
|
|
1039
1044
|
toast.add({
|
|
@@ -1742,6 +1747,7 @@ const displayContext = computed(
|
|
|
1742
1747
|
pageId,
|
|
1743
1748
|
options: optionsForDisplay(activeDisplayId.value),
|
|
1744
1749
|
refresh: refreshAll,
|
|
1750
|
+
reorder: reorderState.value,
|
|
1745
1751
|
table: displayTable.value,
|
|
1746
1752
|
}),
|
|
1747
1753
|
);
|
|
@@ -95,6 +95,47 @@ export interface TableViewDisplayActions<T> {
|
|
|
95
95
|
custom: (action: CustomRowAction) => void;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Rows ordered by hand (backend `reorder`), as a display moves them. Set on
|
|
100
|
+
* the displays that draw the rows in their order (the grid and `cards`), for
|
|
101
|
+
* a caller who may edit them.
|
|
102
|
+
*/
|
|
103
|
+
export interface TableViewDisplayReorder {
|
|
104
|
+
/** Off while a search, a filter or a tab narrows the rows. */
|
|
105
|
+
enabled: boolean;
|
|
106
|
+
/** Moves the row at `from` to `to`, both indexes into `items`. */
|
|
107
|
+
move: (from: number, to: number) => void;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The attributes and listeners of a move handle: `v-bind` them on a
|
|
112
|
+
* `<button>`. Dragged, it moves its row onto the card it is dropped on; with
|
|
113
|
+
* it focused, the arrow keys move the row one place back (up, left) or
|
|
114
|
+
* forward (down, right).
|
|
115
|
+
*/
|
|
116
|
+
export interface TableViewReorderHandle {
|
|
117
|
+
type: "button";
|
|
118
|
+
draggable: boolean;
|
|
119
|
+
disabled: boolean;
|
|
120
|
+
"aria-label": string;
|
|
121
|
+
/** Says how to move the row, or, while moving is off, how to turn it on. */
|
|
122
|
+
title: string;
|
|
123
|
+
onClick: (event: MouseEvent) => void;
|
|
124
|
+
onDragstart: (event: DragEvent) => void;
|
|
125
|
+
onDragend: () => void;
|
|
126
|
+
onKeydown: (event: KeyboardEvent) => void;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** A card's move handle, on a hand-ordered `cards` display. */
|
|
130
|
+
export interface TableViewCardReorder {
|
|
131
|
+
/** Off while a search, a filter or a tab narrows the rows. */
|
|
132
|
+
enabled: boolean;
|
|
133
|
+
/** The card is the one being dragged. */
|
|
134
|
+
dragging: boolean;
|
|
135
|
+
/** Bind on the element the card is moved by. */
|
|
136
|
+
handle: TableViewReorderHandle;
|
|
137
|
+
}
|
|
138
|
+
|
|
98
139
|
/**
|
|
99
140
|
* The card the `kanban` and `cards` displays draw (backend `card` option):
|
|
100
141
|
* the columns it shows, or a component drawing it whole.
|
|
@@ -127,6 +168,11 @@ export interface TableViewCardProps<T = Record<string, unknown>> {
|
|
|
127
168
|
open: () => void;
|
|
128
169
|
/** Kanban only: the value of the column the card sits in. */
|
|
129
170
|
groupValue?: string;
|
|
171
|
+
/**
|
|
172
|
+
* `cards` only, on a hand-ordered table (backend `reorder`) and for a
|
|
173
|
+
* caller who may edit: the card's move handle, to draw somewhere on it.
|
|
174
|
+
*/
|
|
175
|
+
reorder?: TableViewCardReorder;
|
|
130
176
|
}
|
|
131
177
|
|
|
132
178
|
/**
|
|
@@ -182,6 +228,11 @@ export interface TableViewDisplayContext<T = Record<string, unknown>> {
|
|
|
182
228
|
options?: Record<string, unknown>;
|
|
183
229
|
/** Re-fetch the table view (shared list + tab counts + active display). */
|
|
184
230
|
refresh: () => Promise<void> | void;
|
|
231
|
+
/**
|
|
232
|
+
* Rows ordered by hand (backend `reorder`): set on the grid and the
|
|
233
|
+
* `cards` display, for a caller who may edit the rows.
|
|
234
|
+
*/
|
|
235
|
+
reorder?: TableViewDisplayReorder;
|
|
185
236
|
|
|
186
237
|
/**
|
|
187
238
|
* Escape hatch: the TanStack table instance, present when the display is
|
|
@@ -37,8 +37,13 @@ const ButtonStub = defineComponent({
|
|
|
37
37
|
h("button", slots.default?.()),
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
+
// A real UInput wraps its field: the toolbar focuses the input inside it.
|
|
40
41
|
const InputStub = defineComponent({
|
|
41
|
-
|
|
42
|
+
inheritAttrs: false,
|
|
43
|
+
setup:
|
|
44
|
+
(_, { attrs }) =>
|
|
45
|
+
() =>
|
|
46
|
+
h("div", [h("input", attrs)]),
|
|
42
47
|
});
|
|
43
48
|
|
|
44
49
|
let app: App | undefined;
|
|
@@ -88,6 +93,7 @@ function mountActions(searchPlaceholder: string | undefined, search = "") {
|
|
|
88
93
|
app.component(name, stub);
|
|
89
94
|
}
|
|
90
95
|
const container = document.createElement("div");
|
|
96
|
+
document.body.append(container);
|
|
91
97
|
app.mount(container);
|
|
92
98
|
return container;
|
|
93
99
|
}
|
|
@@ -110,6 +116,7 @@ beforeEach(() => {
|
|
|
110
116
|
afterEach(() => {
|
|
111
117
|
app?.unmount();
|
|
112
118
|
app = undefined;
|
|
119
|
+
document.body.replaceChildren();
|
|
113
120
|
vi.unstubAllGlobals();
|
|
114
121
|
});
|
|
115
122
|
|
|
@@ -121,6 +128,20 @@ describe("table toolbar search", () => {
|
|
|
121
128
|
);
|
|
122
129
|
});
|
|
123
130
|
|
|
131
|
+
it("opens the search field from its button and focuses it", async () => {
|
|
132
|
+
const container = mountActions(undefined);
|
|
133
|
+
const trigger = container.querySelector("button");
|
|
134
|
+
trigger?.focus();
|
|
135
|
+
trigger?.click();
|
|
136
|
+
await nextTick();
|
|
137
|
+
await nextTick();
|
|
138
|
+
const input = container.querySelector("input");
|
|
139
|
+
expect(input).not.toBeNull();
|
|
140
|
+
expect(document.activeElement).toBe(input);
|
|
141
|
+
await nextTick();
|
|
142
|
+
expect(container.querySelector("input")).not.toBeNull();
|
|
143
|
+
});
|
|
144
|
+
|
|
124
145
|
it("uses the configured placeholder, which also names the field", async () => {
|
|
125
146
|
const container = mountActions("$page.members.search", "ada");
|
|
126
147
|
await nextTick();
|
|
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
import { tv } from "tailwind-variants";
|
|
4
4
|
import { EYEBROW_CLASS } from "../layers/dms-ui/app/build/utils/eyebrow";
|
|
5
|
+
import { REORDER_HANDLE_CLASS } from "../layers/dms-ui/app/build/composables/table-view/useReorderHandles";
|
|
5
6
|
|
|
6
7
|
const tableSource = readFileSync(
|
|
7
8
|
new URL(
|
|
@@ -17,8 +18,9 @@ const themeSource = tableSource.slice(
|
|
|
17
18
|
const theme = new Function(
|
|
18
19
|
"tv",
|
|
19
20
|
"EYEBROW_CLASS",
|
|
21
|
+
"REORDER_HANDLE_CLASS",
|
|
20
22
|
`${themeSource}; return theme;`,
|
|
21
|
-
)(tv, EYEBROW_CLASS);
|
|
23
|
+
)(tv, EYEBROW_CLASS, REORDER_HANDLE_CLASS);
|
|
22
24
|
|
|
23
25
|
describe("Table grid cell layout theme", () => {
|
|
24
26
|
it.each([undefined, false])("preserves the default for %s", (cellWrap) => {
|
|
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
import { tv } from "tailwind-variants";
|
|
4
4
|
import { EYEBROW_CLASS } from "../layers/dms-ui/app/build/utils/eyebrow";
|
|
5
|
+
import { REORDER_HANDLE_CLASS } from "../layers/dms-ui/app/build/composables/table-view/useReorderHandles";
|
|
5
6
|
import {
|
|
6
7
|
resolveRowClickAction,
|
|
7
8
|
type RowClickActionOptions,
|
|
@@ -22,8 +23,9 @@ const themeSource = tableSource.slice(
|
|
|
22
23
|
const theme = new Function(
|
|
23
24
|
"tv",
|
|
24
25
|
"EYEBROW_CLASS",
|
|
26
|
+
"REORDER_HANDLE_CLASS",
|
|
25
27
|
`${themeSource}; return theme;`,
|
|
26
|
-
)(tv, EYEBROW_CLASS);
|
|
28
|
+
)(tv, EYEBROW_CLASS, REORDER_HANDLE_CLASS);
|
|
27
29
|
|
|
28
30
|
const HOVER_ROW = "hover:bg-elevated";
|
|
29
31
|
const HOVER_CELL = "group-hover:bg-elevated";
|
|
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
import { tv } from "tailwind-variants";
|
|
4
4
|
import { EYEBROW_CLASS } from "../layers/dms-ui/app/build/utils/eyebrow";
|
|
5
|
+
import { REORDER_HANDLE_CLASS } from "../layers/dms-ui/app/build/composables/table-view/useReorderHandles";
|
|
5
6
|
|
|
6
7
|
const tableSource = readFileSync(
|
|
7
8
|
new URL(
|
|
@@ -17,8 +18,9 @@ const themeSource = tableSource.slice(
|
|
|
17
18
|
const theme = new Function(
|
|
18
19
|
"tv",
|
|
19
20
|
"EYEBROW_CLASS",
|
|
21
|
+
"REORDER_HANDLE_CLASS",
|
|
20
22
|
`${themeSource}; return theme;`,
|
|
21
|
-
)(tv, EYEBROW_CLASS);
|
|
23
|
+
)(tv, EYEBROW_CLASS, REORDER_HANDLE_CLASS);
|
|
22
24
|
|
|
23
25
|
describe("Table scroll area", () => {
|
|
24
26
|
it("sticks the header band to the top of its own scroll area", () => {
|
|
@@ -113,7 +113,10 @@ describe("cards display with a custom card", () => {
|
|
|
113
113
|
received.length = 0;
|
|
114
114
|
vi.stubGlobal("computed", computed);
|
|
115
115
|
vi.stubGlobal("h", h);
|
|
116
|
-
vi.stubGlobal("useI18n", () => ({
|
|
116
|
+
vi.stubGlobal("useI18n", () => ({
|
|
117
|
+
locale: ref("en"),
|
|
118
|
+
t: (key: string) => key,
|
|
119
|
+
}));
|
|
117
120
|
vi.stubGlobal("useTranslation", () => ({ processI18n: (t: string) => t }));
|
|
118
121
|
vi.stubGlobal("useDataTypes", () => ({ getDataType: () => undefined }));
|
|
119
122
|
vi.stubGlobal("resolveDmsComponent", (name: string) =>
|
|
@@ -146,7 +149,10 @@ describe("the default cards", () => {
|
|
|
146
149
|
|
|
147
150
|
beforeEach(() => {
|
|
148
151
|
vi.stubGlobal("computed", computed);
|
|
149
|
-
vi.stubGlobal("useI18n", () => ({
|
|
152
|
+
vi.stubGlobal("useI18n", () => ({
|
|
153
|
+
locale: ref("en"),
|
|
154
|
+
t: (key: string) => key,
|
|
155
|
+
}));
|
|
150
156
|
vi.stubGlobal("useTranslation", () => ({ processI18n: (t: string) => t }));
|
|
151
157
|
vi.stubGlobal("useDataTypes", () => ({ getDataType: () => undefined }));
|
|
152
158
|
});
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import {
|
|
3
|
+
computed,
|
|
4
|
+
createApp,
|
|
5
|
+
defineComponent,
|
|
6
|
+
h,
|
|
7
|
+
nextTick,
|
|
8
|
+
reactive,
|
|
9
|
+
ref,
|
|
10
|
+
type App,
|
|
11
|
+
type PropType,
|
|
12
|
+
} from "vue";
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import { resolve } from "node:path";
|
|
16
|
+
import CardsDisplay from "../layers/dms-ui/app/build/components/table-view/CardsDisplay.vue";
|
|
17
|
+
import { useTableReorder } from "../layers/dms-ui/app/build/composables/table-view/useTableReorder";
|
|
18
|
+
import { moveRow } from "../layers/dms-ui/app/build/composables/table-view/utils/reorder";
|
|
19
|
+
import type {
|
|
20
|
+
TableViewCardReorder,
|
|
21
|
+
TableViewDisplayContext,
|
|
22
|
+
} from "../layers/dms-ui/app/composables/table-view/types/display";
|
|
23
|
+
|
|
24
|
+
vi.mock("../layers/dms-ui/app/build/components/table/Pagination.vue", () => ({
|
|
25
|
+
default: { render: () => null },
|
|
26
|
+
}));
|
|
27
|
+
vi.mock("../layers/dms-ui/app/build/components/table/Empty.vue", () => ({
|
|
28
|
+
default: { render: () => null },
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
type Row = { _id: string; name: string };
|
|
32
|
+
type Api = Parameters<typeof useTableReorder>[0]["api"];
|
|
33
|
+
|
|
34
|
+
const tableViewSource = readFileSync(
|
|
35
|
+
resolve(
|
|
36
|
+
__dirname,
|
|
37
|
+
"../layers/dms-ui/app/components/table-view/TableView.vue",
|
|
38
|
+
),
|
|
39
|
+
"utf8",
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const TaskCard = defineComponent({
|
|
43
|
+
props: {
|
|
44
|
+
rowId: String,
|
|
45
|
+
selected: Boolean,
|
|
46
|
+
reorder: Object as PropType<TableViewCardReorder>,
|
|
47
|
+
},
|
|
48
|
+
setup: (props) => () =>
|
|
49
|
+
h("article", { "data-selected": String(props.selected) }, [
|
|
50
|
+
props.rowId,
|
|
51
|
+
props.reorder ? h("button", props.reorder.handle) : null,
|
|
52
|
+
]),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function createContext(enabled = true, componentName?: string) {
|
|
56
|
+
const items = ref<Row[]>(
|
|
57
|
+
["t0", "t1", "t2"].map((id) => ({ _id: id, name: `Task ${id}` })),
|
|
58
|
+
);
|
|
59
|
+
const moves: [number, number][] = [];
|
|
60
|
+
const opened: unknown[] = [];
|
|
61
|
+
const selected = ref<string[]>(["t1"]);
|
|
62
|
+
const reorder = {
|
|
63
|
+
enabled,
|
|
64
|
+
move: (from: number, to: number) => {
|
|
65
|
+
moves.push([from, to]);
|
|
66
|
+
items.value = moveRow(items.value, from, to);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
const context = reactive({
|
|
70
|
+
items,
|
|
71
|
+
columns: [],
|
|
72
|
+
loading: false,
|
|
73
|
+
rowIdKey: "_id",
|
|
74
|
+
labelKey: "name",
|
|
75
|
+
options: componentName ? { card: { component: { componentName } } } : {},
|
|
76
|
+
pagination: { pageIndex: 0, pageSize: 10, total: 3 },
|
|
77
|
+
selection: {
|
|
78
|
+
isSelected: (id: string) => selected.value.includes(id),
|
|
79
|
+
toggle: (id: string, value?: boolean) => {
|
|
80
|
+
const on = value ?? !selected.value.includes(id);
|
|
81
|
+
selected.value = on
|
|
82
|
+
? [...selected.value, id]
|
|
83
|
+
: selected.value.filter((entry) => entry !== id);
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
actions: { canAdd: false, open: (item: unknown) => opened.push(item) },
|
|
87
|
+
reorder,
|
|
88
|
+
}) as unknown as TableViewDisplayContext<Row>;
|
|
89
|
+
return { context, items, moves, opened, selected };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const dragEvent = (type: string) => new Event(type, { bubbles: true });
|
|
93
|
+
const key = (name: string) =>
|
|
94
|
+
new KeyboardEvent("keydown", { key: name, bubbles: true });
|
|
95
|
+
|
|
96
|
+
describe("the cards display of a hand-ordered table", () => {
|
|
97
|
+
let app: App | undefined;
|
|
98
|
+
let container: HTMLElement;
|
|
99
|
+
|
|
100
|
+
const mount = (context: TableViewDisplayContext<Row>) => {
|
|
101
|
+
app = createApp({ render: () => h(CardsDisplay, { context }) });
|
|
102
|
+
app.component(
|
|
103
|
+
"UCheckbox",
|
|
104
|
+
defineComponent({
|
|
105
|
+
emits: ["update:modelValue"],
|
|
106
|
+
setup:
|
|
107
|
+
(_, { emit }) =>
|
|
108
|
+
() =>
|
|
109
|
+
h("input", {
|
|
110
|
+
type: "checkbox",
|
|
111
|
+
onChange: () => emit("update:modelValue", true),
|
|
112
|
+
}),
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
app.component("UIcon", defineComponent({ render: () => h("i") }));
|
|
116
|
+
container = document.createElement("div");
|
|
117
|
+
document.body.append(container);
|
|
118
|
+
app.mount(container);
|
|
119
|
+
};
|
|
120
|
+
const handles = () => [
|
|
121
|
+
...container.querySelectorAll<HTMLButtonElement>("button[draggable]"),
|
|
122
|
+
];
|
|
123
|
+
const cards = () => [...container.querySelectorAll("article")];
|
|
124
|
+
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
vi.stubGlobal("computed", computed);
|
|
127
|
+
vi.stubGlobal("h", h);
|
|
128
|
+
vi.stubGlobal("useI18n", () => ({
|
|
129
|
+
locale: ref("en"),
|
|
130
|
+
t: (text: string) => text,
|
|
131
|
+
}));
|
|
132
|
+
vi.stubGlobal("useTranslation", () => ({ processI18n: (t: string) => t }));
|
|
133
|
+
vi.stubGlobal("useDataTypes", () => ({ getDataType: () => undefined }));
|
|
134
|
+
vi.stubGlobal("resolveDmsComponent", (name: string) =>
|
|
135
|
+
name === "TaskCard" ? TaskCard : undefined,
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
afterEach(() => {
|
|
140
|
+
app?.unmount();
|
|
141
|
+
app = undefined;
|
|
142
|
+
container.remove();
|
|
143
|
+
vi.unstubAllGlobals();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("moves a card dragged by its handle onto the card it is dropped on", async () => {
|
|
147
|
+
const { context, moves, items } = createContext();
|
|
148
|
+
mount(context);
|
|
149
|
+
expect(handles()).toHaveLength(3);
|
|
150
|
+
expect(handles()[0]!.getAttribute("aria-label")).toBe(
|
|
151
|
+
"dms.table.reorder.move",
|
|
152
|
+
);
|
|
153
|
+
handles()[2]!.dispatchEvent(dragEvent("dragstart"));
|
|
154
|
+
await nextTick();
|
|
155
|
+
expect(cards()[2]!.classList.contains("opacity-50")).toBe(true);
|
|
156
|
+
cards()[0]!.dispatchEvent(dragEvent("drop"));
|
|
157
|
+
expect(moves).toEqual([[2, 0]]);
|
|
158
|
+
expect(items.value.map((row) => row._id)).toEqual(["t2", "t0", "t1"]);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("moves a card by one with the arrow keys and keeps the focus on its handle", async () => {
|
|
162
|
+
const { context, moves, items } = createContext();
|
|
163
|
+
mount(context);
|
|
164
|
+
const handle = handles()[1]!;
|
|
165
|
+
handle.focus();
|
|
166
|
+
handle.dispatchEvent(key("ArrowLeft"));
|
|
167
|
+
await nextTick();
|
|
168
|
+
await nextTick();
|
|
169
|
+
expect(items.value.map((row) => row._id)).toEqual(["t1", "t0", "t2"]);
|
|
170
|
+
expect(document.activeElement).toBe(handle);
|
|
171
|
+
handles()[2]!.dispatchEvent(key("ArrowDown"));
|
|
172
|
+
handles()[0]!.dispatchEvent(key("ArrowUp"));
|
|
173
|
+
expect(moves).toEqual([[1, 0]]);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("keeps its selection and its default action beside the handle", () => {
|
|
177
|
+
const { context, opened, selected } = createContext();
|
|
178
|
+
mount(context);
|
|
179
|
+
handles()[0]!.click();
|
|
180
|
+
handles()[0]!.dispatchEvent(key("Enter"));
|
|
181
|
+
expect(opened).toEqual([]);
|
|
182
|
+
container.querySelectorAll("input")[0]!.dispatchEvent(new Event("change"));
|
|
183
|
+
expect(selected.value).toEqual(["t1", "t0"]);
|
|
184
|
+
cards()[2]!.click();
|
|
185
|
+
expect(opened).toEqual([context.items[2]]);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("disables the handle while moving is off, saying how to turn it on", () => {
|
|
189
|
+
const { context, moves } = createContext(false);
|
|
190
|
+
mount(context);
|
|
191
|
+
const handle = container.querySelector("header button")!;
|
|
192
|
+
expect(handle.hasAttribute("disabled")).toBe(true);
|
|
193
|
+
expect(handle.getAttribute("title")).toBe("dms.table.reorder.disabled");
|
|
194
|
+
handle.dispatchEvent(dragEvent("dragstart"));
|
|
195
|
+
cards()[0]!.dispatchEvent(dragEvent("drop"));
|
|
196
|
+
expect(moves).toEqual([]);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("draws no handle on a table not ordered by hand", () => {
|
|
200
|
+
const { context } = createContext();
|
|
201
|
+
(context as { reorder?: unknown }).reorder = undefined;
|
|
202
|
+
mount(context);
|
|
203
|
+
expect(container.querySelector("header button")).toBe(null);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("hands a custom card its handle, its selection, and drops on it", () => {
|
|
207
|
+
const { context, moves } = createContext(true, "TaskCard");
|
|
208
|
+
mount(context);
|
|
209
|
+
expect(cards().map((card) => card.dataset.selected)).toEqual([
|
|
210
|
+
"false",
|
|
211
|
+
"true",
|
|
212
|
+
"false",
|
|
213
|
+
]);
|
|
214
|
+
handles()[0]!.dispatchEvent(dragEvent("dragstart"));
|
|
215
|
+
cards()[2]!.dispatchEvent(dragEvent("drop"));
|
|
216
|
+
expect(moves).toEqual([[0, 2]]);
|
|
217
|
+
handles()[1]!.dispatchEvent(key("ArrowRight"));
|
|
218
|
+
expect(moves).toEqual([
|
|
219
|
+
[0, 2],
|
|
220
|
+
[1, 2],
|
|
221
|
+
]);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe("which displays move rows", () => {
|
|
226
|
+
it("offers moving on the grid and the cards, never on the board or the groups", () => {
|
|
227
|
+
expect(tableViewSource).toMatch(
|
|
228
|
+
/ORDERED_DISPLAY_IDS = new Set\(\[TABLE_DISPLAY_ID, CARDS_DISPLAY_ID\]\)/,
|
|
229
|
+
);
|
|
230
|
+
expect(tableViewSource).toMatch(/reorder: reorderState\.value,/);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("draws no handle on a display that does not draw the rows in their order", () => {
|
|
234
|
+
vi.stubGlobal("computed", computed);
|
|
235
|
+
const { reorderState } = useTableReorder({
|
|
236
|
+
reorder: { field: "position" },
|
|
237
|
+
data: ref(null),
|
|
238
|
+
rowIdKey: "_id",
|
|
239
|
+
location: "/api/features",
|
|
240
|
+
api: vi.fn() as unknown as Api,
|
|
241
|
+
canEdit: computed(() => true),
|
|
242
|
+
isNarrowed: computed(() => false),
|
|
243
|
+
isOrderedDisplay: computed(() => false),
|
|
244
|
+
refresh: vi.fn(),
|
|
245
|
+
onError: vi.fn(),
|
|
246
|
+
});
|
|
247
|
+
expect(reorderState.value).toBe(undefined);
|
|
248
|
+
vi.unstubAllGlobals();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
@@ -67,7 +67,7 @@ describe("rows ordered by hand", () => {
|
|
|
67
67
|
api: api as unknown as Api,
|
|
68
68
|
canEdit: computed(() => true),
|
|
69
69
|
isNarrowed: computed(() => narrowed),
|
|
70
|
-
|
|
70
|
+
isOrderedDisplay: computed(() => true),
|
|
71
71
|
refresh,
|
|
72
72
|
onError,
|
|
73
73
|
});
|
|
@@ -113,7 +113,7 @@ describe("rows ordered by hand", () => {
|
|
|
113
113
|
api: vi.fn() as unknown as Api,
|
|
114
114
|
canEdit: computed(() => false),
|
|
115
115
|
isNarrowed: computed(() => false),
|
|
116
|
-
|
|
116
|
+
isOrderedDisplay: computed(() => true),
|
|
117
117
|
refresh: vi.fn(),
|
|
118
118
|
onError: vi.fn(),
|
|
119
119
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/dms",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/AntelopeJS/dms.git"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@antelopejs/interface-data-api": ">=0.2.0 <1.0.0",
|
|
38
38
|
"@antelopejs/interface-database": ">=0.1.8 <1.0.0",
|
|
39
39
|
"@antelopejs/interface-database-decorators": ">=0.1.7 <1.0.0",
|
|
40
|
-
"@antelopejs/interface-dms": ">=0.5.
|
|
40
|
+
"@antelopejs/interface-dms": ">=0.5.2 <0.6.0",
|
|
41
41
|
"@antelopejs/interface-email": ">=0.0.5 <1.0.0",
|
|
42
42
|
"@antelopejs/interface-file-storage": ">=0.1.3 <1.0.0",
|
|
43
43
|
"archiver": "^7.0.1",
|
package/skills/dms/SKILL.md
CHANGED
|
@@ -53,6 +53,9 @@ For the framework underneath (the core, `ajs` CLI, interfaces as versioned contr
|
|
|
53
53
|
and a `PillsDisplay` item may be `{ label, tone?, variant: "soft" | "outline" }`, styled per row.
|
|
54
54
|
A `KeyValueList` row takes `copy: true` (a copy button for its shown value) or `copyValue`
|
|
55
55
|
(the text to copy instead, e.g. a full URL behind a short label).
|
|
56
|
+
A table ordered by hand (`reorder: { field }`) moves its rows on the grid and its cards on
|
|
57
|
+
the `cards` display alike; a `card.component` receives a `reorder` prop and `v-bind`s
|
|
58
|
+
`reorder.handle` on a button — no custom display just to drag cards.
|
|
56
59
|
- **It's interfaces all the way down.** Everything you consume from the DMS is an AntelopeJS
|
|
57
60
|
interface imported as an `@antelopejs/interface-dms/...` subpath (see the imports below);
|
|
58
61
|
a distributable module exposes its own from a companion `@antelopejs/interface-<name>` package listed in `antelopeJs.implements`.
|