@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.
Files changed (109) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/implementations/dms-base/export-jobs.d.ts +4 -1
  3. package/dist/implementations/dms-base/export-jobs.js +10 -6
  4. package/dist/implementations/dms-base/export-jobs.js.map +1 -1
  5. package/dist/implementations/dms-base/search-route.js +20 -11
  6. package/dist/implementations/dms-base/search-route.js.map +1 -1
  7. package/dist/test/attachment-host/capabilities.d.ts +2 -0
  8. package/dist/test/attachment-host/capabilities.js +11 -0
  9. package/dist/test/attachment-host/capabilities.js.map +1 -1
  10. package/dist/test/integration/table-view-capabilities.test.js +23 -5
  11. package/dist/test/integration/table-view-capabilities.test.js.map +1 -1
  12. package/dist/test/unit/interfaces/dms-base/data-types/field-types.test.js +29 -0
  13. package/dist/test/unit/interfaces/dms-base/data-types/field-types.test.js.map +1 -1
  14. package/dist/test/unit/interfaces/dms-base/display-blocks-catalog.test.js +14 -0
  15. package/dist/test/unit/interfaces/dms-base/display-blocks-catalog.test.js.map +1 -1
  16. package/dist/test/unit/interfaces/dms-base/export-jobs-binding.test.d.ts +1 -0
  17. package/dist/test/unit/interfaces/dms-base/export-jobs-binding.test.js +49 -0
  18. package/dist/test/unit/interfaces/dms-base/export-jobs-binding.test.js.map +1 -0
  19. package/dist/test/unit/interfaces/dms-base/table-view-hidden-columns.test.d.ts +1 -0
  20. package/dist/test/unit/interfaces/dms-base/table-view-hidden-columns.test.js +105 -0
  21. package/dist/test/unit/interfaces/dms-base/table-view-hidden-columns.test.js.map +1 -0
  22. package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js +16 -0
  23. package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js.map +1 -1
  24. package/dist/test/unit/request-tenant-guards.test.js +38 -0
  25. package/dist/test/unit/request-tenant-guards.test.js.map +1 -1
  26. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/accounts.vue +9 -3
  27. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +6 -0
  28. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +6 -0
  29. package/frontend-vue/layers/dms-layout/app/build/components/layout/CommandPaletteAssistantAnswer.vue +1 -0
  30. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +2 -0
  31. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +1 -0
  32. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageSkeleton.vue +13 -4
  33. package/frontend-vue/layers/dms-layout/app/build/components/layout/RolePreviewBar.vue +9 -3
  34. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +5 -1
  35. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePersonalInfo.vue +1 -0
  36. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSummaryRow.vue +4 -0
  37. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RoleEditor.vue +1 -1
  38. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityEmail.vue +4 -1
  39. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityPassword.vue +4 -1
  40. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +1 -0
  41. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +19 -4
  42. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/theme/AppearanceDeveloper.vue +1 -0
  43. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/theme/CssVariablesList.vue +6 -3
  44. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +20 -5
  45. package/frontend-vue/layers/dms-ui/app/build/components/chart/internal/ChartSkeleton.vue +4 -0
  46. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +10 -2
  47. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +7 -2
  48. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +87 -40
  49. package/frontend-vue/layers/dms-ui/app/build/components/skeleton/RowSkeleton.vue +8 -1
  50. package/frontend-vue/layers/dms-ui/app/build/components/stat/StatCell.vue +54 -13
  51. package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +9 -2
  52. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +22 -40
  53. package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +1 -0
  54. package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +1 -0
  55. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +22 -5
  56. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +49 -9
  57. package/frontend-vue/layers/dms-ui/app/build/components/table-view/ExpandedRowDetail.vue +1 -0
  58. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +3 -1
  59. package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +3 -2
  60. package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +25 -0
  61. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useReorderHandles.ts +93 -0
  62. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableReorder.ts +8 -7
  63. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +20 -2
  64. package/frontend-vue/layers/dms-ui/app/build/utils/characterCount.ts +8 -0
  65. package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +4 -1
  66. package/frontend-vue/layers/dms-ui/app/components/blocks/CodeBlock.vue +3 -0
  67. package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +13 -5
  68. package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +7 -1
  69. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +10 -3
  70. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +8 -0
  71. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +4 -3
  72. package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +6 -1
  73. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +5 -1
  74. package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +27 -3
  75. package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +24 -2
  76. package/frontend-vue/layers/dms-ui/app/components/form/components/RichText.vue +13 -4
  77. package/frontend-vue/layers/dms-ui/app/components/form/components/Textarea.vue +27 -2
  78. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +8 -4
  79. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +12 -4
  80. package/frontend-vue/layers/dms-ui/app/components/meter/MeterBlock.vue +15 -4
  81. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +69 -42
  82. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +15 -1
  83. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +13 -3
  84. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +8 -2
  85. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +45 -9
  86. package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +2 -0
  87. package/frontend-vue/layers/dms-ui/app/composables/tab/types/props.ts +5 -0
  88. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +51 -0
  89. package/frontend-vue/tests/confirm-dialog-fields.test.ts +12 -0
  90. package/frontend-vue/tests/drawer-target-direction.test.ts +1 -0
  91. package/frontend-vue/tests/field-suffix-count.test.ts +120 -0
  92. package/frontend-vue/tests/form-section-nav-groups.test.ts +116 -0
  93. package/frontend-vue/tests/nav-card-badge.test.ts +5 -1
  94. package/frontend-vue/tests/nuxt-ui-aria-labels.test.ts +58 -0
  95. package/frontend-vue/tests/page-fill-height.test.ts +2 -2
  96. package/frontend-vue/tests/save-bar.test.ts +49 -0
  97. package/frontend-vue/tests/stat-group-detail-lines.test.ts +63 -0
  98. package/frontend-vue/tests/tab-fit.test.ts +99 -0
  99. package/frontend-vue/tests/table-cell-sublines.test.ts +15 -0
  100. package/frontend-vue/tests/table-cell-wrap.test.ts +27 -3
  101. package/frontend-vue/tests/table-row-action-errors.test.ts +26 -0
  102. package/frontend-vue/tests/table-row-hover.test.ts +3 -1
  103. package/frontend-vue/tests/table-scroll-area.test.ts +3 -1
  104. package/frontend-vue/tests/table-view-card-props.test.ts +8 -2
  105. package/frontend-vue/tests/table-view-cards-reorder.test.ts +250 -0
  106. package/frontend-vue/tests/table-view-reorder.test.ts +2 -2
  107. package/frontend-vue/tests/top-list-card-actions.test.ts +91 -0
  108. package/package.json +2 -2
  109. 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", () => ({ locale: ref("en") }));
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", () => ({ locale: ref("en") }));
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
- isGrid: computed(() => true),
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
- isGrid: computed(() => true),
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.4",
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.1 <0.6.0",
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",
@@ -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`.