@antelopejs/dms 0.4.8 → 0.5.1

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 (82) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/hooks/invite-extensions.js +0 -2
  3. package/dist/hooks/invite-extensions.js.map +1 -1
  4. package/dist/implementations/dms/page.js +6 -7
  5. package/dist/implementations/dms/page.js.map +1 -1
  6. package/dist/implementations/dms/realtime.d.ts +2 -2
  7. package/dist/implementations/dms/realtime.js +9 -3
  8. package/dist/implementations/dms/realtime.js.map +1 -1
  9. package/dist/implementations/dms-base/table-view.d.ts +5 -0
  10. package/dist/implementations/dms-base/table-view.js +7 -1
  11. package/dist/implementations/dms-base/table-view.js.map +1 -1
  12. package/dist/pages/settings/users/member-invite-form.d.ts +5 -0
  13. package/dist/pages/settings/users/member-invite-form.js +11 -2
  14. package/dist/pages/settings/users/member-invite-form.js.map +1 -1
  15. package/dist/pages/settings/users/members.d.ts +2 -0
  16. package/dist/pages/settings/users/members.js +11 -0
  17. package/dist/pages/settings/users/members.js.map +1 -1
  18. package/dist/realtime/index.js +0 -2
  19. package/dist/realtime/index.js.map +1 -1
  20. package/dist/realtime/registry.d.ts +1 -0
  21. package/dist/realtime/registry.js +9 -0
  22. package/dist/realtime/registry.js.map +1 -1
  23. package/dist/realtime/table-view.d.ts +3 -0
  24. package/dist/realtime/table-view.js +25 -0
  25. package/dist/realtime/table-view.js.map +1 -0
  26. package/dist/test/antelope.test.js +6 -0
  27. package/dist/test/antelope.test.js.map +1 -1
  28. package/dist/test/helpers/reload-host.d.ts +9 -0
  29. package/dist/test/helpers/reload-host.js +21 -0
  30. package/dist/test/helpers/reload-host.js.map +1 -0
  31. package/dist/test/integration/invite-resolution.test.js +0 -2
  32. package/dist/test/integration/invite-resolution.test.js.map +1 -1
  33. package/dist/test/reload-host/index.d.ts +2 -0
  34. package/dist/test/reload-host/index.js +32 -0
  35. package/dist/test/reload-host/index.js.map +1 -0
  36. package/dist/test/reload-host/package.json +8 -0
  37. package/dist/test/unit/frontend-renderer-range.test.d.ts +1 -0
  38. package/dist/test/unit/frontend-renderer-range.test.js +45 -0
  39. package/dist/test/unit/frontend-renderer-range.test.js.map +1 -0
  40. package/dist/test/unit/interfaces/dms/invite-extension.test.js +22 -24
  41. package/dist/test/unit/interfaces/dms/invite-extension.test.js.map +1 -1
  42. package/dist/test/unit/interfaces/dms/module-reload-registrations.test.d.ts +1 -0
  43. package/dist/test/unit/interfaces/dms/module-reload-registrations.test.js +51 -0
  44. package/dist/test/unit/interfaces/dms/module-reload-registrations.test.js.map +1 -0
  45. package/dist/test/unit/interfaces/dms/page-topics.test.d.ts +1 -0
  46. package/dist/test/unit/interfaces/dms/page-topics.test.js +194 -0
  47. package/dist/test/unit/interfaces/dms/page-topics.test.js.map +1 -0
  48. package/dist/test/unit/interfaces/dms-base/layouts.test.js +16 -0
  49. package/dist/test/unit/interfaces/dms-base/layouts.test.js.map +1 -1
  50. package/dist/test/unit/interfaces/dms-base/table-view-evaluation-order.fixture.d.ts +5 -0
  51. package/dist/test/unit/interfaces/dms-base/table-view-evaluation-order.fixture.js +82 -0
  52. package/dist/test/unit/interfaces/dms-base/table-view-evaluation-order.fixture.js.map +1 -0
  53. package/dist/test/unit/interfaces/dms-base/table-view-evaluation-order.test.d.ts +1 -0
  54. package/dist/test/unit/interfaces/dms-base/table-view-evaluation-order.test.js +24 -0
  55. package/dist/test/unit/interfaces/dms-base/table-view-evaluation-order.test.js.map +1 -0
  56. package/dist/test/unit/interfaces/dms-base/table-view-get-guard.test.js +10 -4
  57. package/dist/test/unit/interfaces/dms-base/table-view-get-guard.test.js.map +1 -1
  58. package/dist/test/unit/interfaces/dms-base/table-view-realtime.test.d.ts +1 -0
  59. package/dist/test/unit/interfaces/dms-base/table-view-realtime.test.js +139 -0
  60. package/dist/test/unit/interfaces/dms-base/table-view-realtime.test.js.map +1 -0
  61. package/dist/test/unit/pages/settings/invite-availability.test.d.ts +1 -0
  62. package/dist/test/unit/pages/settings/invite-availability.test.js +112 -0
  63. package/dist/test/unit/pages/settings/invite-availability.test.js.map +1 -0
  64. package/dist/test/unit/utils/owned-registry.test.d.ts +1 -0
  65. package/dist/test/unit/utils/owned-registry.test.js +61 -0
  66. package/dist/test/unit/utils/owned-registry.test.js.map +1 -0
  67. package/dist/validation/auth.schema.d.ts +2 -2
  68. package/frontend-vue/layers/dms-layout/app/composables/layout/usePageFillHeight.ts +37 -0
  69. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +9 -0
  70. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +16 -1
  71. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +18 -8
  72. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +12 -0
  73. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/custom-button.ts +4 -0
  74. package/frontend-vue/package.json +4 -1
  75. package/frontend-vue/pnpm-lock.yaml +8 -8
  76. package/frontend-vue/tests/page-fill-height.test.ts +352 -0
  77. package/package.json +6 -6
  78. package/skills/dms-module/REFERENCE.md +2 -1
  79. package/skills/dms-module/SKILL.md +5 -0
  80. package/dist/realtime/tableview-bridge.d.ts +0 -1
  81. package/dist/realtime/tableview-bridge.js +0 -38
  82. package/dist/realtime/tableview-bridge.js.map +0 -1
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const interface_api_1 = require("@antelopejs/interface-api");
37
+ const interface_core_1 = require("@antelopejs/interface-core");
38
+ const internal_1 = require("@antelopejs/interface-core/internal");
39
+ const chai_1 = require("chai");
40
+ const table_view_1 = require("../../../../implementations/dms-base/table-view");
41
+ const tableViewInterface = __importStar(require("@antelopejs/interface-dms/base/table-view"));
42
+ const table_view_2 = require("@antelopejs/interface-dms/base/table-view");
43
+ const realtime_1 = require("@antelopejs/interface-dms/base/table-view/realtime");
44
+ const LOCATION = "/api/table-view-realtime";
45
+ const SESSION = "table-view-realtime-session";
46
+ const PRESENCE_ACQUIRE = "1";
47
+ const USER = { _id: "table-view-realtime-user", name: "Realtime" };
48
+ const REMOVED_SETTERS = [
49
+ "setRealtimeMutationHook",
50
+ "setRealtimePresenceHook",
51
+ "setRealtimePageTopicHook",
52
+ ];
53
+ class RowsAPI extends (0, interface_api_1.Controller)(LOCATION) {
54
+ }
55
+ const controller = new RowsAPI();
56
+ const deliveries = [];
57
+ const editRoute = (0, realtime_1.withRealtimeMutation)({ eventType: "updated", extractIds: realtime_1.extractSingleParamId }, { method: "post", args: [], func: async () => ({ ok: true }) });
58
+ const getRoute = (0, realtime_1.withPresenceAcquire)({
59
+ method: "get",
60
+ args: [],
61
+ func: async () => ({ ok: true }),
62
+ });
63
+ function edit(id) {
64
+ return editRoute.func.call(controller, undefined, { id }, SESSION, USER);
65
+ }
66
+ function open(id) {
67
+ return getRoute.func.call(controller, undefined, { id }, SESSION, PRESENCE_ACQUIRE, USER);
68
+ }
69
+ function generation(name) {
70
+ return {
71
+ internal: {
72
+ PublishMutation: async ({ eventType, ids }) => {
73
+ deliveries.push(`${name} ${eventType} ${ids.join(",")}`);
74
+ },
75
+ AcquirePresence: async ({ rowId }) => {
76
+ deliveries.push(`${name} presence ${rowId}`);
77
+ },
78
+ },
79
+ };
80
+ }
81
+ function attach(implementation) {
82
+ (0, interface_core_1.ImplementInterface)({ internal: table_view_2.internal }, implementation);
83
+ }
84
+ function detachGeneration() {
85
+ for (const fn of [table_view_2.internal.PublishMutation, table_view_2.internal.AcquirePresence]) {
86
+ fn.proxy.detach();
87
+ }
88
+ }
89
+ function settle() {
90
+ return new Promise((resolve) => setImmediate(resolve));
91
+ }
92
+ describe("[unit] interfaces/dms-base — table-view realtime across DMS generations", () => {
93
+ beforeEach(() => {
94
+ deliveries.length = 0;
95
+ });
96
+ after(() => {
97
+ attach({ internal: table_view_1.internal });
98
+ });
99
+ it("delivers to the generation that implements the interface now", async () => {
100
+ attach(generation("first"));
101
+ await edit("row-1");
102
+ await open("row-1");
103
+ attach(generation("second"));
104
+ await edit("row-2");
105
+ await open("row-2");
106
+ (0, chai_1.expect)(deliveries).to.deep.equal([
107
+ "first updated row-1",
108
+ "first presence row-1",
109
+ "second updated row-2",
110
+ "second presence row-2",
111
+ ]);
112
+ });
113
+ it("holds what a route sends between two generations for the next one", async () => {
114
+ attach(generation("first"));
115
+ detachGeneration();
116
+ const stubMode = internal_1.internal.testStubMode;
117
+ internal_1.internal.testStubMode = false;
118
+ try {
119
+ const routes = Promise.all([edit("row-3"), open("row-3")]);
120
+ await settle();
121
+ (0, chai_1.expect)(deliveries).to.deep.equal([]);
122
+ attach(generation("second"));
123
+ await routes;
124
+ }
125
+ finally {
126
+ internal_1.internal.testStubMode = stubMode;
127
+ }
128
+ (0, chai_1.expect)(deliveries).to.deep.equal([
129
+ "second updated row-3",
130
+ "second presence row-3",
131
+ ]);
132
+ });
133
+ it("offers no setter to leave a DMS callback in the interface", () => {
134
+ for (const setter of REMOVED_SETTERS) {
135
+ (0, chai_1.expect)(tableViewInterface).to.not.have.property(setter);
136
+ }
137
+ });
138
+ });
139
+ //# sourceMappingURL=table-view-realtime.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-view-realtime.test.js","sourceRoot":"","sources":["../../../../../src/test/unit/interfaces/dms-base/table-view-realtime.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const interface_core_1 = require("@antelopejs/interface-core");
37
+ const chai_1 = require("chai");
38
+ const invite_extensions_1 = require("@antelopejs/interface-dms/invite-extensions");
39
+ const page_1 = require("@antelopejs/interface-dms/page");
40
+ const permissionsInterface = __importStar(require("@antelopejs/interface-dms/permissions"));
41
+ const permissionsResolverInterface = __importStar(require("@antelopejs/interface-dms/permissions-resolver"));
42
+ const tenantAccessInterface = __importStar(require("@antelopejs/interface-dms/tenant-access"));
43
+ const page_2 = require("../../../../implementations/dms/page");
44
+ const permissionsImpl = __importStar(require("../../../../implementations/dms/permissions"));
45
+ const permissionsResolverImpl = __importStar(require("../../../../implementations/dms/permissions-resolver"));
46
+ const tenantAccessImpl = __importStar(require("../../../../implementations/dms/tenant-access"));
47
+ const member_invite_form_1 = require("../../../../pages/settings/users/member-invite-form");
48
+ const members_1 = require("../../../../pages/settings/users/members");
49
+ const page_access_1 = require("../../../helpers/page-access");
50
+ const FULL_TENANT = "invite-availability-full-tenant";
51
+ const OPEN_TENANT = "invite-availability-open-tenant";
52
+ const SEAT_LIMIT_REASON = "$test.seat_limit_reached";
53
+ const INVITER = { _id: "invite-availability-inviter" };
54
+ function membersPageSlug() {
55
+ const pageInfo = (0, interface_core_1.GetMetadata)(members_1.MembersSettingsController, page_1.PageMetadata).pageInfo;
56
+ if (!pageInfo)
57
+ throw new Error("The members page is not registered");
58
+ return pageInfo.fullSlug;
59
+ }
60
+ async function inviteButtonFor(tenantId) {
61
+ const handler = (0, page_1.GetPageLayoutBySlug)(membersPageSlug());
62
+ if (!handler)
63
+ throw new Error("The members page serves no layout");
64
+ const { memberModel, roleModel } = (0, page_access_1.stubPageAccessModels)(["*"]);
65
+ const layout = await handler(INVITER, memberModel, roleModel, tenantId);
66
+ const table = layout.components
67
+ .table;
68
+ return table.options?.customButtons?.find((button) => button.id === members_1.MEMBER_INVITE_BUTTON_ID);
69
+ }
70
+ describe("[unit] pages/settings/users/members — invite availability", () => {
71
+ const cleanups = [];
72
+ before(() => {
73
+ (0, interface_core_1.ImplementInterface)(permissionsInterface, permissionsImpl);
74
+ (0, interface_core_1.ImplementInterface)(permissionsResolverInterface, permissionsResolverImpl);
75
+ (0, interface_core_1.ImplementInterface)(tenantAccessInterface, tenantAccessImpl);
76
+ (0, interface_core_1.ImplementInterface)(page_1.internal, page_2.internal);
77
+ });
78
+ afterEach(() => {
79
+ cleanups.splice(0).forEach((cleanup) => cleanup());
80
+ });
81
+ it("serves the invite button enabled while no module refuses", async () => {
82
+ const button = await inviteButtonFor(OPEN_TENANT);
83
+ (0, chai_1.expect)(button).to.exist;
84
+ (0, chai_1.expect)(button?.disabled).to.equal(undefined);
85
+ (0, chai_1.expect)(button).to.not.have.property("availability");
86
+ });
87
+ it("disables the invite button with the reason a module gives", async () => {
88
+ cleanups.push((0, invite_extensions_1.RegisterInviteAvailability)(({ tenantId }) => tenantId === FULL_TENANT ? { reason: SEAT_LIMIT_REASON } : undefined));
89
+ (0, chai_1.expect)(await inviteButtonFor(FULL_TENANT)).to.deep.include({
90
+ disabled: true,
91
+ disabledReason: SEAT_LIMIT_REASON,
92
+ });
93
+ (0, chai_1.expect)((await inviteButtonFor(OPEN_TENANT))?.disabled).to.equal(undefined);
94
+ });
95
+ it("leaves the invite button enabled when a resolver throws", async () => {
96
+ cleanups.push((0, invite_extensions_1.RegisterInviteAvailability)(() => {
97
+ throw new Error("resolver failure");
98
+ }));
99
+ (0, chai_1.expect)((await inviteButtonFor(FULL_TENANT))?.disabled).to.equal(undefined);
100
+ });
101
+ });
102
+ describe("[unit] pages/settings/users/member-invite-form — invitation language", () => {
103
+ it("defaults to the inviter's own language", () => {
104
+ (0, chai_1.expect)((0, member_invite_form_1.resolveInviteLanguage)("fr")).to.equal("fr");
105
+ (0, chai_1.expect)((0, member_invite_form_1.resolveInviteLanguage)("fr-FR")).to.equal("fr");
106
+ });
107
+ it("falls back to English for a language invitations are not offered in", () => {
108
+ (0, chai_1.expect)((0, member_invite_form_1.resolveInviteLanguage)("de")).to.equal("en");
109
+ (0, chai_1.expect)((0, member_invite_form_1.resolveInviteLanguage)(undefined)).to.equal("en");
110
+ });
111
+ });
112
+ //# sourceMappingURL=invite-availability.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invite-availability.test.js","sourceRoot":"","sources":["../../../../../src/test/unit/pages/settings/invite-availability.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const modules_1 = require("@antelopejs/interface-core/modules");
4
+ const owned_registry_1 = require("@antelopejs/interface-dms/utils/owned-registry");
5
+ const chai_1 = require("chai");
6
+ function generation(module, index) {
7
+ return { module, owner: `${module}#${index}` };
8
+ }
9
+ function addAs(context, registry, value) {
10
+ (0, modules_1.RunWithModuleContext)(context, () => registry.add(value));
11
+ }
12
+ function destroy(context) {
13
+ (0, modules_1.RunWithModuleContext)(context, () => {
14
+ modules_1.Events.ModuleDestroyed.emit(context.module);
15
+ });
16
+ }
17
+ describe("[unit] utils/owned-registry", () => {
18
+ it("releases what a generation added when it is destroyed, and only that", () => {
19
+ const registry = new owned_registry_1.OwnedRegistry();
20
+ const reloaded = generation("owned-registry-reloaded", 1);
21
+ const other = generation("owned-registry-other", 1);
22
+ addAs(reloaded, registry, "reloaded");
23
+ addAs(other, registry, "other");
24
+ destroy(reloaded);
25
+ (0, chai_1.expect)(registry.values()).to.deep.equal(["other"]);
26
+ destroy(other);
27
+ (0, chai_1.expect)(registry.values()).to.deep.equal([]);
28
+ });
29
+ it("keeps what the next generation of the same module adds", () => {
30
+ const registry = new owned_registry_1.OwnedRegistry();
31
+ const first = generation("owned-registry-next", 1);
32
+ const next = generation("owned-registry-next", 2);
33
+ addAs(first, registry, "first");
34
+ destroy(first);
35
+ addAs(next, registry, "next");
36
+ (0, chai_1.expect)(registry.values()).to.deep.equal(["next"]);
37
+ destroy(next);
38
+ });
39
+ it("lists the live values in the order they were added", () => {
40
+ const registry = new owned_registry_1.OwnedRegistry();
41
+ const owner = generation("owned-registry-order", 1);
42
+ for (const value of ["first", "second", "third"]) {
43
+ addAs(owner, registry, value);
44
+ }
45
+ (0, chai_1.expect)(registry.values()).to.deep.equal(["first", "second", "third"]);
46
+ destroy(owner);
47
+ });
48
+ it("removes only the oldest registration a matcher accepts", () => {
49
+ const registry = new owned_registry_1.OwnedRegistry();
50
+ const owner = generation("owned-registry-remove", 1);
51
+ for (const value of ["twice", "once", "twice"]) {
52
+ addAs(owner, registry, value);
53
+ }
54
+ registry.remove((value) => value === "twice");
55
+ (0, chai_1.expect)(registry.values()).to.deep.equal(["once", "twice"]);
56
+ (0, chai_1.expect)(registry.has((value) => value === "once")).to.equal(true);
57
+ (0, chai_1.expect)(registry.has((value) => value === "never")).to.equal(false);
58
+ destroy(owner);
59
+ });
60
+ });
61
+ //# sourceMappingURL=owned-registry.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owned-registry.test.js","sourceRoot":"","sources":["../../../../src/test/unit/utils/owned-registry.test.ts"],"names":[],"mappings":"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"}
@@ -42,14 +42,14 @@ export declare const authSchema: {
42
42
  code: string;
43
43
  state: string;
44
44
  state_cookie: string;
45
- invite?: string | undefined;
46
45
  language?: string | undefined;
46
+ invite?: string | undefined;
47
47
  }, {
48
48
  code: string;
49
49
  state: string;
50
50
  state_cookie: string;
51
- invite?: string | undefined;
52
51
  language?: string | undefined;
52
+ invite?: string | undefined;
53
53
  }>;
54
54
  refresh: z.ZodObject<{
55
55
  token: z.ZodString;
@@ -0,0 +1,37 @@
1
+ import type { InjectionKey, Ref } from "vue";
2
+
3
+ const PAGE_FILL_HEIGHT_KEY: InjectionKey<Readonly<Ref<boolean>>> = Symbol(
4
+ "dms:page-fill-height",
5
+ );
6
+
7
+ /**
8
+ * The flex column a page filling the panel (`DefaultLayout({ fillHeight })`)
9
+ * is laid out in, from the scrolling panel body down to the component that
10
+ * takes the height left.
11
+ */
12
+ export const PAGE_FILL_HEIGHT_CLASSES = {
13
+ /**
14
+ * The page region, the panel body's only child. The minimum height sits
15
+ * here rather than on the component that fills: a floor set lower down
16
+ * overflows this region, and the panel then scrolls past its own bottom
17
+ * padding.
18
+ */
19
+ region: "flex min-h-[30rem] flex-1 flex-col",
20
+ /** The page stack, and the component wrapper that takes the height left. */
21
+ column: "flex min-h-0 flex-1 flex-col",
22
+ } as const;
23
+
24
+ /**
25
+ * Lets the page rendered in a layout's slot know whether the layout gives it
26
+ * the height of the panel.
27
+ */
28
+ export const providePageFillHeight = (fillHeight: Readonly<Ref<boolean>>) => {
29
+ provide(PAGE_FILL_HEIGHT_KEY, fillHeight);
30
+ };
31
+
32
+ /**
33
+ * Whether the layout rendering the page asked it to fill the panel height.
34
+ * `false` under a layout that does not provide it.
35
+ */
36
+ export const usePageFillHeight = (): Readonly<Ref<boolean>> =>
37
+ inject(PAGE_FILL_HEIGHT_KEY, () => ref(false), true);
@@ -8,6 +8,7 @@ import DashboardBanners from "../build/components/layout/DashboardBanners.vue";
8
8
  interface Props {
9
9
  fullWidth?: boolean;
10
10
  hideHeader?: boolean;
11
+ fillHeight?: boolean;
11
12
  icon?: string;
12
13
  title?: string;
13
14
  description?: string;
@@ -16,10 +17,17 @@ interface Props {
16
17
  const props = withDefaults(defineProps<Props>(), {
17
18
  fullWidth: true,
18
19
  hideHeader: false,
20
+ fillHeight: false,
19
21
  icon: "i-ph-file",
20
22
  title: undefined,
21
23
  description: undefined,
22
24
  });
25
+
26
+ providePageFillHeight(toRef(props, "fillHeight"));
27
+
28
+ const regionClass = computed(() =>
29
+ props.fillHeight ? PAGE_FILL_HEIGHT_CLASSES.region : undefined,
30
+ );
23
31
  </script>
24
32
 
25
33
  <template>
@@ -37,6 +45,7 @@ const props = withDefaults(defineProps<Props>(), {
37
45
  data-dms-page-region
38
46
  data-dms-page-content
39
47
  :full-width="props.fullWidth"
48
+ :class="regionClass"
40
49
  >
41
50
  <PageHeader
42
51
  v-if="props.title && !props.hideHeader"
@@ -339,6 +339,17 @@ const components = computed(() =>
339
339
  }),
340
340
  );
341
341
 
342
+ // A page filling the panel (`DefaultLayout({ fillHeight })`) gives the height
343
+ // left under its header to its last component; the ones above it keep theirs.
344
+ const fillHeight = usePageFillHeight();
345
+ const fillColumnClass = PAGE_FILL_HEIGHT_CLASSES.column;
346
+ const stackClass = computed(() =>
347
+ fillHeight.value ? fillColumnClass : undefined,
348
+ );
349
+ const fillingComponentId = computed(() =>
350
+ fillHeight.value ? components.value.at(-1)?.id : undefined,
351
+ );
352
+
342
353
  // --- Live-edit highlight ---------------------------------------------------
343
354
  // When the active page's layout changes in place (DMS dev hot-reload, same
344
355
  // route), animate the components that changed. Added/removed components are
@@ -457,12 +468,16 @@ if (import.meta.env.DEV) {
457
468
  tag="div"
458
469
  name="dms-fresh"
459
470
  class="dms-page-stack space-y-6"
471
+ :class="stackClass"
460
472
  :css="animateLayout"
461
473
  >
462
474
  <div
463
475
  v-for="component in components"
464
476
  :key="component.id"
465
- :class="{ 'dms-fresh-modified': freshComponentIds.has(component.id) }"
477
+ :class="[
478
+ { 'dms-fresh-modified': freshComponentIds.has(component.id) },
479
+ component.id === fillingComponentId && fillColumnClass,
480
+ ]"
466
481
  >
467
482
  <DmsRecursiveComponent
468
483
  :component="component"
@@ -244,15 +244,25 @@ const uiTableActions = computed(() => uiTableActionsVariant());
244
244
  @click="tableSharedData!.emits('add')"
245
245
  />
246
246
 
247
- <UButton
247
+ <!-- A disabled button fires no pointer event: the tooltip hangs on a
248
+ wrapper, focusable so the reason also reaches keyboard users. -->
249
+ <UTooltip
248
250
  v-for="(button, index) in customButtons"
249
251
  :key="`custom-btn-${index}`"
250
- :label="processI18n(button.label)"
251
- :icon="button.icon"
252
- :variant="button.variant"
253
- :color="button.color"
254
- size="sm"
255
- @click="props.onCustomButton?.(button)"
256
- />
252
+ :text="button.disabledReason && processI18n(button.disabledReason)"
253
+ :disabled="!button.disabled || !button.disabledReason"
254
+ >
255
+ <span :tabindex="button.disabled ? 0 : undefined" class="inline-flex">
256
+ <UButton
257
+ :label="processI18n(button.label)"
258
+ :icon="button.icon"
259
+ :variant="button.variant"
260
+ :color="button.color"
261
+ :disabled="button.disabled"
262
+ size="sm"
263
+ @click="props.onCustomButton?.(button)"
264
+ />
265
+ </span>
266
+ </UTooltip>
257
267
  </div>
258
268
  </template>
@@ -795,7 +795,19 @@ export const useTableRowActions = <T extends Data>(
795
795
  }
796
796
  };
797
797
 
798
+ // Reached for a disabled button only through a quick action, which presses
799
+ // it by id: the reason is told instead of the target being opened.
798
800
  const handleCustomButton = (button: CustomButton) => {
801
+ if (button.disabled) {
802
+ toast.add({
803
+ color: Color.warning,
804
+ title: processI18n(button.label),
805
+ description: button.disabledReason
806
+ ? processI18n(button.disabledReason)
807
+ : undefined,
808
+ });
809
+ return;
810
+ }
799
811
  handleActionTarget(button.target, button.label);
800
812
  };
801
813
 
@@ -10,4 +10,8 @@ export interface CustomButton {
10
10
  variant?: CustomButtonVariant;
11
11
  color?: ColorValue;
12
12
  target: ActionTarget;
13
+ /** Set by the server when the button cannot be pressed for this request. */
14
+ disabled?: boolean;
15
+ /** Why the button is disabled, shown in its tooltip. May be an i18n key. */
16
+ disabledReason?: string;
13
17
  }
@@ -10,6 +10,9 @@
10
10
  },
11
11
  "license": "Apache-2.0",
12
12
  "packageManager": "pnpm@10.6.5",
13
+ "engines": {
14
+ "@antelopejs/dms-frontend": ">=0.3.1 <0.4.0"
15
+ },
13
16
  "scripts": {
14
17
  "typecheck": "ajs dms verify-source --layer \"$PWD\" --local-package @antelopejs/dms=.. --local-package @antelopejs/interface-dms=../../interface-dms",
15
18
  "test": "vitest run",
@@ -58,7 +61,7 @@
58
61
  "zod": "^3.24.2"
59
62
  },
60
63
  "devDependencies": {
61
- "@antelopejs/core": ">=1.9.1 <2",
64
+ "@antelopejs/core": ">=1.10.1 <2",
62
65
  "@antelopejs/dms-frontend": ">=0.3.2 <1.0.0",
63
66
  "@eslint/js": "^8.57.0",
64
67
  "@inertiajs/vue3": "^3.7.0",
@@ -121,11 +121,11 @@ importers:
121
121
  version: 3.25.76
122
122
  devDependencies:
123
123
  '@antelopejs/core':
124
- specifier: '>=1.9.1 <2'
125
- version: 1.9.1(@types/node@24.8.1)
124
+ specifier: '>=1.10.1 <2'
125
+ version: 1.10.1(@types/node@24.8.1)
126
126
  '@antelopejs/dms-frontend':
127
127
  specifier: '>=0.3.2 <1.0.0'
128
- version: 0.3.2(@antelopejs/core@1.9.1(@types/node@24.8.1))
128
+ version: 0.3.2(@antelopejs/core@1.10.1(@types/node@24.8.1))
129
129
  '@eslint/js':
130
130
  specifier: ^8.57.0
131
131
  version: 8.57.0
@@ -196,8 +196,8 @@ packages:
196
196
  resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
197
197
  engines: {node: '>=10'}
198
198
 
199
- '@antelopejs/core@1.9.1':
200
- resolution: {integrity: sha512-MPLJak0rPluV4lgsdsO5ba03sgTxIGSncajBcoaoppuppQmksdpaemojIHiVLJ4LiG0iiH21Roaed8XXy1XkYQ==}
199
+ '@antelopejs/core@1.10.1':
200
+ resolution: {integrity: sha512-NcC6N8Aq14Ph7GRto0lpdRdgUKZAu3ZHDlAF5hXGFZPKKoNm8Da9i4nyB/NI4Sgx3rbE0/KoqCcHpTtQJgrAYg==}
201
201
  hasBin: true
202
202
 
203
203
  '@antelopejs/dms-frontend@0.3.2':
@@ -4312,7 +4312,7 @@ snapshots:
4312
4312
 
4313
4313
  '@alloc/quick-lru@5.2.0': {}
4314
4314
 
4315
- '@antelopejs/core@1.9.1(@types/node@24.8.1)':
4315
+ '@antelopejs/core@1.10.1(@types/node@24.8.1)':
4316
4316
  dependencies:
4317
4317
  '@antelopejs/interface-core': 0.1.1
4318
4318
  '@types/proper-lockfile': 4.1.4
@@ -4331,7 +4331,7 @@ snapshots:
4331
4331
  transitivePeerDependencies:
4332
4332
  - '@types/node'
4333
4333
 
4334
- '@antelopejs/dms-frontend@0.3.2(@antelopejs/core@1.9.1(@types/node@24.8.1))':
4334
+ '@antelopejs/dms-frontend@0.3.2(@antelopejs/core@1.10.1(@types/node@24.8.1))':
4335
4335
  dependencies:
4336
4336
  boxen: 5.1.2
4337
4337
  chalk: 4.1.2
@@ -4342,7 +4342,7 @@ snapshots:
4342
4342
  semver: 7.8.5
4343
4343
  unzipper: 0.12.5
4344
4344
  optionalDependencies:
4345
- '@antelopejs/core': 1.9.1(@types/node@24.8.1)
4345
+ '@antelopejs/core': 1.10.1(@types/node@24.8.1)
4346
4346
  '@oxc-parser/binding-darwin-arm64': 0.95.0
4347
4347
  '@oxc-parser/binding-darwin-x64': 0.95.0
4348
4348
  '@oxc-parser/binding-linux-arm64-gnu': 0.95.0