@antelopejs/dms 0.7.4 → 0.7.6
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 +37 -0
- package/dist/implementations/dms-base/export-jobs.d.ts +4 -1
- package/dist/implementations/dms-base/export-jobs.js +10 -6
- package/dist/implementations/dms-base/export-jobs.js.map +1 -1
- package/dist/implementations/dms-base/search-route.js +20 -11
- package/dist/implementations/dms-base/search-route.js.map +1 -1
- package/dist/test/attachment-host/capabilities.d.ts +2 -0
- package/dist/test/attachment-host/capabilities.js +11 -0
- package/dist/test/attachment-host/capabilities.js.map +1 -1
- package/dist/test/integration/table-view-capabilities.test.js +23 -5
- package/dist/test/integration/table-view-capabilities.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/data-types/field-types.test.js +29 -0
- package/dist/test/unit/interfaces/dms-base/data-types/field-types.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/display-blocks-catalog.test.js +14 -0
- package/dist/test/unit/interfaces/dms-base/display-blocks-catalog.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/export-jobs-binding.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/export-jobs-binding.test.js +49 -0
- package/dist/test/unit/interfaces/dms-base/export-jobs-binding.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-hidden-columns.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-hidden-columns.test.js +105 -0
- package/dist/test/unit/interfaces/dms-base/table-view-hidden-columns.test.js.map +1 -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/dist/test/unit/request-tenant-guards.test.js +38 -0
- package/dist/test/unit/request-tenant-guards.test.js.map +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/accounts.vue +9 -3
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +6 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +6 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/CommandPaletteAssistantAnswer.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +2 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageSkeleton.vue +13 -4
- package/frontend-vue/layers/dms-layout/app/build/components/layout/RolePreviewBar.vue +9 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +5 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePersonalInfo.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSummaryRow.vue +4 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RoleEditor.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityEmail.vue +4 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityPassword.vue +4 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +19 -4
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/theme/AppearanceDeveloper.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/theme/CssVariablesList.vue +6 -3
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +20 -5
- package/frontend-vue/layers/dms-ui/app/build/components/chart/internal/ChartSkeleton.vue +4 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +10 -2
- package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +7 -2
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +87 -40
- package/frontend-vue/layers/dms-ui/app/build/components/skeleton/RowSkeleton.vue +8 -1
- package/frontend-vue/layers/dms-ui/app/build/components/stat/StatCell.vue +54 -13
- package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +9 -2
- package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +22 -40
- package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +1 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +1 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +22 -5
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +49 -9
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/ExpandedRowDetail.vue +1 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +3 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +3 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +25 -0
- 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/build/composables/table-view/useTableViewRowActions.ts +20 -2
- package/frontend-vue/layers/dms-ui/app/build/utils/characterCount.ts +8 -0
- package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/blocks/CodeBlock.vue +3 -0
- package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +13 -5
- package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +7 -1
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +10 -3
- package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +8 -0
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +4 -3
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +6 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +5 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +27 -3
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +24 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/RichText.vue +13 -4
- package/frontend-vue/layers/dms-ui/app/components/form/components/Textarea.vue +27 -2
- package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +8 -4
- package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +12 -4
- package/frontend-vue/layers/dms-ui/app/components/meter/MeterBlock.vue +15 -4
- package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +69 -42
- package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +15 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +13 -3
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +8 -2
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +45 -9
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/composables/tab/types/props.ts +5 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +51 -0
- package/frontend-vue/tests/confirm-dialog-fields.test.ts +12 -0
- package/frontend-vue/tests/drawer-target-direction.test.ts +1 -0
- package/frontend-vue/tests/field-suffix-count.test.ts +120 -0
- package/frontend-vue/tests/form-section-nav-groups.test.ts +116 -0
- package/frontend-vue/tests/nav-card-badge.test.ts +5 -1
- package/frontend-vue/tests/nuxt-ui-aria-labels.test.ts +58 -0
- package/frontend-vue/tests/page-fill-height.test.ts +2 -2
- package/frontend-vue/tests/save-bar.test.ts +49 -0
- package/frontend-vue/tests/stat-group-detail-lines.test.ts +63 -0
- package/frontend-vue/tests/tab-fit.test.ts +99 -0
- package/frontend-vue/tests/table-cell-sublines.test.ts +15 -0
- package/frontend-vue/tests/table-cell-wrap.test.ts +27 -3
- package/frontend-vue/tests/table-row-action-errors.test.ts +26 -0
- 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/frontend-vue/tests/top-list-card-actions.test.ts +91 -0
- package/package.json +2 -2
- package/skills/dms/SKILL.md +14 -1
|
@@ -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
|
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A TopListCard's head carries link buttons ("Catalogue →") after its period
|
|
3
|
+
* badge, quiet like an ActivityFeed's; a card without them draws its head as
|
|
4
|
+
* before.
|
|
5
|
+
*/
|
|
6
|
+
import { createSSRApp, defineComponent, h, ref } from "vue";
|
|
7
|
+
import { renderToString } from "vue/server-renderer";
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
9
|
+
import TopListCard from "../layers/dms-ui/app/components/top-list/TopListCard.vue";
|
|
10
|
+
|
|
11
|
+
vi.mock("../layers/dms-ui/app/composables/chart/useChartFetch", () => ({
|
|
12
|
+
useChartFetch: () => ({
|
|
13
|
+
data: ref({ items: [{ id: "pro", title: "Pro", value: 12 }] }),
|
|
14
|
+
isLoading: ref(false),
|
|
15
|
+
}),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const Slot = defineComponent({
|
|
19
|
+
setup:
|
|
20
|
+
(_, { slots }) =>
|
|
21
|
+
() =>
|
|
22
|
+
h("div", slots.default?.()),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const Button = defineComponent({
|
|
26
|
+
props: { label: String, to: String, variant: String, color: String },
|
|
27
|
+
setup: (props) => () =>
|
|
28
|
+
h(
|
|
29
|
+
"a",
|
|
30
|
+
{
|
|
31
|
+
"data-button": "",
|
|
32
|
+
href: props.to,
|
|
33
|
+
"data-variant": props.variant,
|
|
34
|
+
"data-color": props.color,
|
|
35
|
+
},
|
|
36
|
+
props.label,
|
|
37
|
+
),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
async function render(props: Record<string, unknown>): Promise<string> {
|
|
41
|
+
const app = createSSRApp({
|
|
42
|
+
render: () =>
|
|
43
|
+
h(TopListCard, {
|
|
44
|
+
title: "Top plans",
|
|
45
|
+
componentId: "plans",
|
|
46
|
+
pageId: "dashboard",
|
|
47
|
+
...props,
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
for (const name of ["DmsCard", "UBadge", "UIcon", "USkeleton"]) {
|
|
51
|
+
app.component(name, Slot);
|
|
52
|
+
}
|
|
53
|
+
app.component("UButton", Button);
|
|
54
|
+
app.component("DmsTopListRow", Slot);
|
|
55
|
+
return renderToString(app);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
vi.stubGlobal("useI18n", () => ({ locale: ref("en-US"), t: String }));
|
|
60
|
+
vi.stubGlobal("useTranslation", () => ({
|
|
61
|
+
processI18n: (text: string) => (text.startsWith("$") ? `t(${text})` : text),
|
|
62
|
+
}));
|
|
63
|
+
vi.stubGlobal("useComponentEvent", () => ({}));
|
|
64
|
+
vi.stubGlobal("useWatch", () => ({ state: ref({}) }));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
68
|
+
|
|
69
|
+
describe("TopListCard actions", () => {
|
|
70
|
+
it("draws its link buttons in the head, quiet and translated", async () => {
|
|
71
|
+
const html = await render({
|
|
72
|
+
actions: [
|
|
73
|
+
{
|
|
74
|
+
label: "$dashboard.catalogue",
|
|
75
|
+
to: "/plans",
|
|
76
|
+
icon: "i-ph-arrow-right",
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
const button = /<a data-button[^>]*href="\/plans"[^>]*>([^<]*)<\/a>/.exec(
|
|
81
|
+
html,
|
|
82
|
+
);
|
|
83
|
+
expect(button?.[1]).toBe("t($dashboard.catalogue)");
|
|
84
|
+
expect(button?.[0]).toContain('data-variant="ghost"');
|
|
85
|
+
expect(button?.[0]).toContain('data-color="neutral"');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("draws no button without actions", async () => {
|
|
89
|
+
expect(await render({})).not.toContain("data-button");
|
|
90
|
+
});
|
|
91
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/dms",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6",
|
|
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.3 <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
|
@@ -48,11 +48,24 @@ For the framework underneath (the core, `ajs` CLI, interfaces as versioned contr
|
|
|
48
48
|
banners (`RegisterLayoutBanner({ resolve })` → `{ text, actions }`) take it; never format text server-side.
|
|
49
49
|
A table cell's secondary line is a row field holding one (or `{ text, tone }`), named by
|
|
50
50
|
`StatusPillDisplay({ subField })`, `IdentityDisplay({ subtitleField })` or
|
|
51
|
-
`TwoLineDisplay({ subField })`; `IdentityDisplay({ emptyLabel, emptyIcon })` draws an empty row
|
|
51
|
+
`TwoLineDisplay({ subField })`; `IdentityDisplay({ emptyLabel, emptyIcon })` draws an empty row,
|
|
52
|
+
and `IdentityDisplay({ iconField })` reads each row's tile icon from a row field.
|
|
52
53
|
`StatusPillDisplay({ toneField })` reads the pill's tone from a row field (an HTTP status class),
|
|
53
54
|
and a `PillsDisplay` item may be `{ label, tone?, variant: "soft" | "outline" }`, styled per row.
|
|
55
|
+
A `StatGroup` detail and a `NavCardGrid` card title take up to two lines before their ellipsis.
|
|
56
|
+
A unit after a number field is `NumberType({ suffix: "$…days" })`, a character counter on a
|
|
57
|
+
text field `StringType({ maxLength, showCount: true })` — no custom `inputComponent` for either.
|
|
58
|
+
A `TableView` over a shared controller hides a column on one page with
|
|
59
|
+
`columns: { hidden: ["workspace"] }` (as `isVisible: false`, for that table view only) — no view
|
|
60
|
+
just to hide it.
|
|
61
|
+
A long sectioned `Form` groups its side navigation with `FormSection.navGroup` ("Billing", "Legal").
|
|
62
|
+
`TopListCard({ actions })` puts link buttons in its head ("Catalogue →"), and `Tab({ fit: true })`
|
|
63
|
+
keeps a pill tab list to its tabs' width.
|
|
54
64
|
A `KeyValueList` row takes `copy: true` (a copy button for its shown value) or `copyValue`
|
|
55
65
|
(the text to copy instead, e.g. a full URL behind a short label).
|
|
66
|
+
A table ordered by hand (`reorder: { field }`) moves its rows on the grid and its cards on
|
|
67
|
+
the `cards` display alike; a `card.component` receives a `reorder` prop and `v-bind`s
|
|
68
|
+
`reorder.handle` on a button — no custom display just to drag cards.
|
|
56
69
|
- **It's interfaces all the way down.** Everything you consume from the DMS is an AntelopeJS
|
|
57
70
|
interface imported as an `@antelopejs/interface-dms/...` subpath (see the imports below);
|
|
58
71
|
a distributable module exposes its own from a companion `@antelopejs/interface-<name>` package listed in `antelopeJs.implements`.
|