@antelopejs/dms 0.4.5 → 0.4.7
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 +47 -0
- package/dist/hooks/invite-extensions.js +2 -2
- package/dist/hooks/invite-extensions.js.map +1 -1
- package/dist/implementations/dms/layout-banners.d.ts +18 -0
- package/dist/implementations/dms/layout-banners.js +60 -0
- package/dist/implementations/dms/layout-banners.js.map +1 -0
- package/dist/implementations/dms/page.d.ts +2 -0
- package/dist/implementations/dms/page.js +82 -27
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms/quick-actions.d.ts +5 -0
- package/dist/implementations/dms/quick-actions.js.map +1 -1
- package/dist/implementations/dms/resolver-timeout.d.ts +1 -0
- package/dist/implementations/dms/resolver-timeout.js +13 -0
- package/dist/implementations/dms/resolver-timeout.js.map +1 -0
- package/dist/implementations/dms/saas-mode.d.ts +8 -0
- package/dist/implementations/dms/saas-mode.js +20 -0
- package/dist/implementations/dms/saas-mode.js.map +1 -0
- package/dist/implementations/dms-auth/index.d.ts +2 -1
- package/dist/implementations/dms-auth/index.js +19 -8
- package/dist/implementations/dms-auth/index.js.map +1 -1
- package/dist/implementations/dms-html-render/service-jwt.js +2 -1
- package/dist/implementations/dms-html-render/service-jwt.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/pages/settings/users/index.d.ts +1 -0
- package/dist/pages/settings/users/index.js +1 -0
- package/dist/pages/settings/users/index.js.map +1 -1
- package/dist/pages/settings/users/invite-extension-routes.d.ts +4 -0
- package/dist/pages/settings/users/invite-extension-routes.js +57 -0
- package/dist/pages/settings/users/invite-extension-routes.js.map +1 -0
- package/dist/pages/settings/users/invites.d.ts +2 -1
- package/dist/pages/settings/users/invites.js +17 -5
- package/dist/pages/settings/users/invites.js.map +1 -1
- package/dist/pages/settings/users/members.d.ts +4 -1
- package/dist/pages/settings/users/members.js +10 -6
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.js +1 -1
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/pages/settings/users/quick-actions.d.ts +1 -0
- package/dist/pages/settings/users/quick-actions.js +22 -0
- package/dist/pages/settings/users/quick-actions.js.map +1 -0
- package/dist/routes/auth/signup.d.ts +2 -0
- package/dist/routes/auth/signup.js +7 -2
- package/dist/routes/auth/signup.js.map +1 -1
- package/dist/test/integration/invite-extension-edit.test.d.ts +1 -0
- package/dist/test/integration/invite-extension-edit.test.js +122 -0
- package/dist/test/integration/invite-extension-edit.test.js.map +1 -0
- package/dist/test/integration/saas-mode-ownership.test.d.ts +1 -0
- package/dist/test/integration/saas-mode-ownership.test.js +81 -0
- package/dist/test/integration/saas-mode-ownership.test.js.map +1 -0
- package/dist/test/integration/tenant-lifecycle.test.js +38 -0
- package/dist/test/integration/tenant-lifecycle.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/layout-banners.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/layout-banners.test.js +217 -0
- package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -0
- package/dist/test/unit/implementations/dms/quick-actions-button.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/quick-actions-button.test.js +220 -0
- package/dist/test/unit/implementations/dms/quick-actions-button.test.js.map +1 -0
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js +1 -1
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js.map +1 -1
- package/dist/test/unit/implementations/dms-html-render/service-jwt.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms-html-render/service-jwt.test.js +37 -0
- package/dist/test/unit/implementations/dms-html-render/service-jwt.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/invite-extension.test.js +138 -12
- package/dist/test/unit/interfaces/dms/invite-extension.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/password-policy.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/password-policy.test.js +43 -0
- package/dist/test/unit/interfaces/dms/password-policy.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/saas-mode.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/saas-mode.test.js +42 -0
- package/dist/test/unit/interfaces/dms/saas-mode.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/tenant-invite-email.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/tenant-invite-email.test.js +53 -0
- package/dist/test/unit/interfaces/dms/tenant-invite-email.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.js +80 -0
- package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.js.map +1 -0
- package/dist/test/unit/pages/settings/invite-member-quick-action.test.d.ts +1 -0
- package/dist/test/unit/pages/settings/invite-member-quick-action.test.js +104 -0
- package/dist/test/unit/pages/settings/invite-member-quick-action.test.js.map +1 -0
- package/dist/test/unit/pages/settings/notifications-access-gate.test.d.ts +1 -0
- package/dist/test/unit/pages/settings/notifications-access-gate.test.js +78 -0
- package/dist/test/unit/pages/settings/notifications-access-gate.test.js.map +1 -0
- package/dist/test/unit/signup-account-guard.test.js +20 -2
- package/dist/test/unit/signup-account-guard.test.js.map +1 -1
- package/dist/test/unit/utils/admin-invite-email.test.d.ts +1 -0
- package/dist/test/unit/utils/admin-invite-email.test.js +69 -0
- package/dist/test/unit/utils/admin-invite-email.test.js.map +1 -0
- package/dist/utils/admin-invite-email.d.ts +14 -0
- package/dist/utils/admin-invite-email.js +51 -0
- package/dist/utils/admin-invite-email.js.map +1 -0
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/validation/auth.schema.d.ts +3 -3
- package/dist/validation/auth.schema.js +1 -1
- package/dist/validation/auth.schema.js.map +1 -1
- package/dist/validation/password.d.ts +0 -1
- package/dist/validation/password.js +6 -3
- package/dist/validation/password.js.map +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +37 -8
- package/frontend-vue/layers/dms-auth/i18n/locales/auth-en-GB.json +3 -1
- package/frontend-vue/layers/dms-auth/i18n/locales/auth-fr-FR.json +3 -1
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +4 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +4 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +50 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +16 -13
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +2 -2
- package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +11 -5
- package/frontend-vue/layers/dms-layout/app/build/components/notification/widgetRequest.ts +20 -0
- package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +59 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useQuickActions.ts +15 -2
- package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +2 -0
- package/frontend-vue/layers/dms-layout/app/emails/EmailAdminInvite.vue +54 -9
- package/frontend-vue/layers/dms-layout/app/error.vue +17 -2
- package/frontend-vue/layers/dms-layout/app/types/page.ts +22 -0
- package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +48 -0
- package/frontend-vue/layers/dms-layout/app/utils/favorites.ts +94 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +21 -2
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +21 -2
- package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/index.ts +1 -1
- package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/{useOnboardingMiddleware.ts → useOnboarding.ts} +0 -12
- package/frontend-vue/layers/dms-onboarding/app/custom-pages/onboarding.vue +4 -1
- package/frontend-vue/layers/dms-onboarding/app/middleware/onboarding.global.ts +21 -4
- package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +13 -3
- package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +13 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +2 -7
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/columnVisibility.ts +13 -0
- package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/activity/ActivityItem.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/banner/Banner.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +2 -2
- package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +6 -16
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +2 -2
- package/frontend-vue/layers/dms-ui/app/components/chart/internal/TrendBadge.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/copy/CopyButton.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +3 -3
- package/frontend-vue/layers/dms-ui/app/components/master-detail/MasterDetail.vue +5 -1
- package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/status/StatusSummary.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +35 -11
- package/frontend-vue/layers/dms-ui/app/components/tree/Tree.vue +10 -9
- package/frontend-vue/layers/dms-ui/app/composables/chart/donutRecords.ts +26 -0
- package/frontend-vue/layers/dms-ui/app/composables/tab/types/props.ts +4 -2
- package/frontend-vue/layers/dms-ui/app/composables/tab/useTab.ts +3 -3
- package/frontend-vue/layers/dms-ui/app/composables/table-view/shortcuts/index.ts +17 -4
- package/frontend-vue/layers/dms-ui/app/composables/table-view/shortcuts/tableViewEscape.ts +22 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/column.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/custom-button.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/composables/tree/shortcuts/index.ts +19 -4
- package/frontend-vue/layers/dms-ui/app/composables/tree/shortcuts/treeEscape.ts +21 -0
- package/frontend-vue/layers/dms-ui/app/types/quick-actions.ts +38 -3
- package/frontend-vue/layers/dms-ui/app/utils/overlayFocus.ts +16 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +4 -2
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +4 -2
- package/frontend-vue/package.json +1 -1
- package/frontend-vue/pnpm-lock.yaml +5 -5
- package/frontend-vue/tests/admin-invite-email.test.ts +86 -0
- package/frontend-vue/tests/breadcrumb-target.test.ts +50 -0
- package/frontend-vue/tests/chart-nested-donut.test.ts +97 -0
- package/frontend-vue/tests/error-page-message.test.ts +83 -0
- package/frontend-vue/tests/favorite-page.test.ts +104 -0
- package/frontend-vue/tests/icon-aria-hidden.test.ts +29 -0
- package/frontend-vue/tests/layout-banners.test.ts +224 -0
- package/frontend-vue/tests/notification-popover-errors.test.ts +147 -0
- package/frontend-vue/tests/onboarding-middleware.test.ts +103 -0
- package/frontend-vue/tests/password-strength-meter.test.ts +33 -0
- package/frontend-vue/tests/quick-action-intent.test.ts +74 -0
- package/frontend-vue/tests/quick-actions.test.ts +23 -0
- package/frontend-vue/tests/signup-invitation-link.test.ts +126 -0
- package/frontend-vue/tests/skeleton-paragraph.test.ts +30 -0
- package/frontend-vue/tests/tab-without-component-id.test.ts +51 -0
- package/frontend-vue/tests/table-column-visibility.test.ts +20 -0
- package/frontend-vue/tests/table-view-escape-shortcut.test.ts +141 -0
- package/frontend-vue/tests/tree-escape-shortcut.test.ts +138 -0
- package/frontend-vue/tests/vue-inventory.test.ts +1 -1
- package/package.json +2 -2
- package/skills/dms/REFERENCE.md +1 -1
|
@@ -155,10 +155,10 @@ const primaryDotColor = computed(() => resolveChartColor("primary"));
|
|
|
155
155
|
<div class="mb-4 flex flex-wrap items-start justify-between gap-3">
|
|
156
156
|
<div class="min-w-0">
|
|
157
157
|
<p class="text-muted text-sm">{{ processI18n(title) }}</p>
|
|
158
|
-
<
|
|
158
|
+
<div class="text-2xl font-semibold tracking-tight tabular-nums">
|
|
159
159
|
<USkeleton v-if="isLoading && data === null" class="h-8 w-32" />
|
|
160
160
|
<span v-else>{{ formatted }}</span>
|
|
161
|
-
</
|
|
161
|
+
</div>
|
|
162
162
|
<div
|
|
163
163
|
v-if="
|
|
164
164
|
(showDelta && delta !== null) || previousFormatted || description
|
|
@@ -51,7 +51,7 @@ const display = computed(() => {
|
|
|
51
51
|
class="inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs font-medium whitespace-nowrap tabular-nums transition-colors"
|
|
52
52
|
:class="colorClass"
|
|
53
53
|
>
|
|
54
|
-
<UIcon :name="icon" class="size-3" aria-hidden="true" />
|
|
54
|
+
<UIcon :name="icon" class="size-3" :aria-hidden="true" />
|
|
55
55
|
{{ display }}
|
|
56
56
|
</span>
|
|
57
57
|
</template>
|
|
@@ -118,7 +118,7 @@ const iconWrapClass = computed(() =>
|
|
|
118
118
|
<div class="flex items-start justify-between gap-3">
|
|
119
119
|
<div class="min-w-0 flex-1">
|
|
120
120
|
<p :class="labelClass">{{ processI18n(title) }}</p>
|
|
121
|
-
<
|
|
121
|
+
<div class="mt-1 text-2xl font-semibold tracking-tight tabular-nums">
|
|
122
122
|
<USkeleton v-if="isLoading && data === null" class="h-8 w-24" />
|
|
123
123
|
<span v-else class="inline-flex items-baseline gap-x-1">
|
|
124
124
|
<span
|
|
@@ -135,7 +135,7 @@ const iconWrapClass = computed(() =>
|
|
|
135
135
|
{{ formattedParts.unit }}
|
|
136
136
|
</span>
|
|
137
137
|
</span>
|
|
138
|
-
</
|
|
138
|
+
</div>
|
|
139
139
|
<div
|
|
140
140
|
v-if="showTrend || compareLabel || description"
|
|
141
141
|
class="mt-2 flex min-w-0 items-center gap-x-2"
|
|
@@ -153,7 +153,7 @@ const iconWrapClass = computed(() =>
|
|
|
153
153
|
<UIcon
|
|
154
154
|
:name="resolvedIcon"
|
|
155
155
|
:class="isStat ? 'size-4' : 'size-5'"
|
|
156
|
-
aria-hidden="true"
|
|
156
|
+
:aria-hidden="true"
|
|
157
157
|
/>
|
|
158
158
|
</div>
|
|
159
159
|
</div>
|
|
@@ -49,7 +49,11 @@ defineSlots<{
|
|
|
49
49
|
class="grid size-[26px] shrink-0 place-items-center"
|
|
50
50
|
:class="selected === item.value ? 'text-primary' : 'text-toned'"
|
|
51
51
|
>
|
|
52
|
-
<UIcon
|
|
52
|
+
<UIcon
|
|
53
|
+
:name="item.icon"
|
|
54
|
+
class="size-[18px]"
|
|
55
|
+
:aria-hidden="true"
|
|
56
|
+
/>
|
|
53
57
|
</span>
|
|
54
58
|
<span class="min-w-0">
|
|
55
59
|
<span class="text-highlighted block text-sm font-semibold">
|
|
@@ -68,7 +68,7 @@ const resolvedIcon = computed(() => props.icon || DEFAULT_ICON[props.status]);
|
|
|
68
68
|
:class="RING[status]"
|
|
69
69
|
class="grid size-16 shrink-0 place-items-center rounded-full border-2"
|
|
70
70
|
>
|
|
71
|
-
<UIcon :name="resolvedIcon" class="size-[30px]" aria-hidden="true" />
|
|
71
|
+
<UIcon :name="resolvedIcon" class="size-[30px]" :aria-hidden="true" />
|
|
72
72
|
</div>
|
|
73
73
|
<div>
|
|
74
74
|
<p
|
|
@@ -7,11 +7,13 @@ import type {
|
|
|
7
7
|
Table,
|
|
8
8
|
VisibilityState,
|
|
9
9
|
} from "@tanstack/vue-table";
|
|
10
|
-
import { refDebounced, watchDebounced } from "@vueuse/core";
|
|
10
|
+
import { refDebounced, useActiveElement, watchDebounced } from "@vueuse/core";
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
QUICK_ACTION_BUTTON_KEY,
|
|
13
13
|
QUICK_ACTION_COMPONENT_KEY,
|
|
14
14
|
QUICK_ACTION_QUERY_KEY,
|
|
15
|
+
type QuickActionIntent,
|
|
16
|
+
readQuickActionIntent,
|
|
15
17
|
} from "../../types/quick-actions";
|
|
16
18
|
import type {
|
|
17
19
|
KanbanConfig,
|
|
@@ -957,24 +959,45 @@ async function clearQuickActionQuery() {
|
|
|
957
959
|
const {
|
|
958
960
|
[QUICK_ACTION_QUERY_KEY]: _discarded,
|
|
959
961
|
[QUICK_ACTION_COMPONENT_KEY]: _discardedComponent,
|
|
962
|
+
[QUICK_ACTION_BUTTON_KEY]: _discardedButton,
|
|
960
963
|
...rest
|
|
961
964
|
} = route.query;
|
|
962
965
|
await router.replace({ query: rest });
|
|
963
966
|
}
|
|
964
967
|
|
|
965
|
-
//
|
|
966
|
-
//
|
|
967
|
-
function
|
|
968
|
-
|
|
968
|
+
// The button is looked up among the ones the server kept for this caller: a
|
|
969
|
+
// button it stripped cannot be pressed through the URL either.
|
|
970
|
+
function pressCustomButton(id: string) {
|
|
971
|
+
const button = customButtons?.find((candidate) => candidate.id === id);
|
|
972
|
+
if (button) handleCustomButton(button);
|
|
969
973
|
}
|
|
970
974
|
|
|
975
|
+
const quickActionHandlers: {
|
|
976
|
+
[K in QuickActionIntent["kind"]]: (
|
|
977
|
+
intent: Extract<QuickActionIntent, { kind: K }>,
|
|
978
|
+
) => void;
|
|
979
|
+
} = {
|
|
980
|
+
add: () => handleRowAdd(),
|
|
981
|
+
button: (intent) => pressCustomButton(intent.button),
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
// Watched key by key rather than through the parsed intent: a fresh intent
|
|
985
|
+
// object on every unrelated query change would press the button twice.
|
|
971
986
|
watch(
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
987
|
+
[
|
|
988
|
+
QUICK_ACTION_QUERY_KEY,
|
|
989
|
+
QUICK_ACTION_COMPONENT_KEY,
|
|
990
|
+
QUICK_ACTION_BUTTON_KEY,
|
|
991
|
+
].map((key) => () => route.query[key]),
|
|
992
|
+
async () => {
|
|
993
|
+
const intent = readQuickActionIntent(route.query, componentId);
|
|
994
|
+
if (!intent) return;
|
|
976
995
|
await clearQuickActionQuery();
|
|
977
|
-
|
|
996
|
+
// Keyed by the intent's own discriminant, so the handler always matches.
|
|
997
|
+
const handler = quickActionHandlers[intent.kind] as (
|
|
998
|
+
value: QuickActionIntent,
|
|
999
|
+
) => void;
|
|
1000
|
+
handler(intent);
|
|
978
1001
|
},
|
|
979
1002
|
{ immediate: true },
|
|
980
1003
|
);
|
|
@@ -987,6 +1010,7 @@ defineShortcuts(
|
|
|
987
1010
|
globalFilter,
|
|
988
1011
|
rowSelect,
|
|
989
1012
|
data: shownData,
|
|
1013
|
+
activeElement: useActiveElement(),
|
|
990
1014
|
}),
|
|
991
1015
|
);
|
|
992
1016
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { useActiveElement } from "@vueuse/core";
|
|
2
3
|
import type { TreeNode, TreeProps } from "../../composables/tree/types";
|
|
3
4
|
import {
|
|
4
5
|
TreeSelectionBehavior,
|
|
@@ -120,15 +121,15 @@ function navigateTree(direction: TreeNavigationDirection) {
|
|
|
120
121
|
navigationHandlers[direction]();
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
buildTreeShortcuts
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
})
|
|
131
|
-
|
|
124
|
+
defineShortcuts(
|
|
125
|
+
buildTreeShortcuts({
|
|
126
|
+
navigateTree,
|
|
127
|
+
props,
|
|
128
|
+
items,
|
|
129
|
+
selected,
|
|
130
|
+
activeElement: useActiveElement(),
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
132
133
|
</script>
|
|
133
134
|
|
|
134
135
|
<template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ChartSeries, DonutRecord } from "./types";
|
|
2
|
+
|
|
3
|
+
type ChartSeriesEntry = ChartSeries["data"][number];
|
|
4
|
+
|
|
5
|
+
interface LabelledPoint {
|
|
6
|
+
label?: string;
|
|
7
|
+
x?: unknown;
|
|
8
|
+
y?: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function toDonutRecord(entry: ChartSeriesEntry): DonutRecord {
|
|
12
|
+
if (typeof entry === "number") {
|
|
13
|
+
return { label: String(entry), value: entry };
|
|
14
|
+
}
|
|
15
|
+
const point = entry as LabelledPoint;
|
|
16
|
+
return {
|
|
17
|
+
label: point.label ?? String(point.x ?? ""),
|
|
18
|
+
value: Number(point.y ?? 0),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Turns one chart series into the slices of a circular chart. */
|
|
23
|
+
export function toDonutRecords(series: ChartSeries | undefined): DonutRecord[] {
|
|
24
|
+
if (!series || !Array.isArray(series.data)) return [];
|
|
25
|
+
return series.data.map(toDonutRecord);
|
|
26
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { TabItem } from "./item";
|
|
2
2
|
|
|
3
|
+
// A tab set is also used directly in hand-written pages, outside any
|
|
4
|
+
// registered component: the ids exist only for server-driven ones.
|
|
3
5
|
interface TabComponentProps {
|
|
4
|
-
componentId
|
|
5
|
-
pageId
|
|
6
|
+
componentId?: string;
|
|
7
|
+
pageId?: string;
|
|
6
8
|
routeParams?: Record<string, string>;
|
|
7
9
|
watchActions?: WatchAction[];
|
|
8
10
|
childCount?: number;
|
|
@@ -80,14 +80,14 @@ export function useTab(
|
|
|
80
80
|
};
|
|
81
81
|
|
|
82
82
|
watch(activeTab, async (newTab, oldTab) => {
|
|
83
|
-
if (newTab
|
|
83
|
+
if (newTab === oldTab) return;
|
|
84
|
+
if (props.componentId) {
|
|
84
85
|
sendComponentEvent(TabEvents.TAB_CHANGE, props.componentId, {
|
|
85
86
|
previousTab: Number.parseInt(oldTab, 10),
|
|
86
87
|
currentTab: Number.parseInt(newTab, 10),
|
|
87
88
|
});
|
|
88
|
-
|
|
89
|
-
await updateUrl(newTab);
|
|
90
89
|
}
|
|
90
|
+
await updateUrl(newTab);
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
watch(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { computed, type ComputedRef, type ShallowRef } from "vue";
|
|
2
2
|
import type { RowSelectionState } from "@tanstack/vue-table";
|
|
3
3
|
import type {
|
|
4
4
|
TableRowActionOptions,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
TABLE_VIEW_ESCAPE_METADATA,
|
|
17
17
|
createTableViewEscapeShortcut,
|
|
18
|
+
isTableViewEscapeActive,
|
|
18
19
|
} from "./tableViewEscape";
|
|
19
20
|
import {
|
|
20
21
|
TABLE_VIEW_SHIFT_A_METADATA,
|
|
@@ -42,8 +43,11 @@ interface BuildTableViewShortcutsParams<T extends Data> {
|
|
|
42
43
|
globalFilter: ShallowRef<string | undefined>;
|
|
43
44
|
rowSelect: Ref<RowSelectionState>;
|
|
44
45
|
data: Ref<{ results: T[] } | null | undefined>;
|
|
46
|
+
activeElement: Ref<Element | null | undefined>;
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
type TableViewShortcuts = Record<string, (() => void) | undefined>;
|
|
50
|
+
|
|
47
51
|
export function buildTableViewShortcuts<T extends Data>({
|
|
48
52
|
refresh,
|
|
49
53
|
tableProps,
|
|
@@ -51,11 +55,20 @@ export function buildTableViewShortcuts<T extends Data>({
|
|
|
51
55
|
globalFilter,
|
|
52
56
|
rowSelect,
|
|
53
57
|
data,
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
activeElement,
|
|
59
|
+
}: BuildTableViewShortcutsParams<T>): ComputedRef<TableViewShortcuts> {
|
|
60
|
+
const shortcuts = {
|
|
56
61
|
meta_r: createTableViewMetaRShortcut(refresh),
|
|
57
62
|
shift_n: createTableViewShiftNShortcut(tableProps, newRow),
|
|
58
|
-
escape: createTableViewEscapeShortcut(globalFilter, rowSelect),
|
|
59
63
|
shift_a: createTableViewShiftAShortcut(tableProps, data, rowSelect),
|
|
60
64
|
};
|
|
65
|
+
const escape = createTableViewEscapeShortcut(globalFilter, rowSelect);
|
|
66
|
+
// An unregistered key is left untouched by `defineShortcuts`, so Escape
|
|
67
|
+
// reaches overlays whenever the table has nothing to clear.
|
|
68
|
+
return computed(() => ({
|
|
69
|
+
...shortcuts,
|
|
70
|
+
escape: isTableViewEscapeActive(globalFilter, rowSelect, activeElement)
|
|
71
|
+
? escape
|
|
72
|
+
: undefined,
|
|
73
|
+
}));
|
|
61
74
|
}
|
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
import type { ShallowRef } from "vue";
|
|
2
2
|
import type { RowSelectionState } from "@tanstack/vue-table";
|
|
3
|
+
import { isFocusInsideOverlay } from "../../../utils/overlayFocus";
|
|
3
4
|
|
|
4
5
|
export const TABLE_VIEW_ESCAPE_METADATA: ShortcutMetadata = {
|
|
5
6
|
key: ["$keyboard.escape"],
|
|
6
7
|
descriptionKey: "$dms.shortcuts.tableview.escape.description",
|
|
7
8
|
component: "$dms.components.tableview",
|
|
9
|
+
condition: {
|
|
10
|
+
descriptionKey: "$dms.shortcuts.tableview.escape.condition",
|
|
11
|
+
},
|
|
8
12
|
};
|
|
9
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Nuxt UI's `defineShortcuts` calls `preventDefault()` on every key it
|
|
16
|
+
* handles, and reka-ui's DismissableLayer ignores a prevented Escape. The
|
|
17
|
+
* shortcut is therefore only registered when there is a search or a
|
|
18
|
+
* selection to clear, and never while focus sits inside an overlay, so the
|
|
19
|
+
* overlay above the table keeps closing on Escape.
|
|
20
|
+
*/
|
|
21
|
+
export function isTableViewEscapeActive(
|
|
22
|
+
globalFilter: ShallowRef<string | undefined>,
|
|
23
|
+
rowSelect: Ref<RowSelectionState>,
|
|
24
|
+
activeElement: Ref<Element | null | undefined>,
|
|
25
|
+
): boolean {
|
|
26
|
+
if (isFocusInsideOverlay(activeElement.value)) return false;
|
|
27
|
+
return (
|
|
28
|
+
globalFilter.value !== undefined || Object.keys(rowSelect.value).length > 0
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
10
32
|
export function createTableViewEscapeShortcut(
|
|
11
33
|
globalFilter: ShallowRef<string | undefined>,
|
|
12
34
|
rowSelect: Ref<RowSelectionState>,
|
|
@@ -3,6 +3,8 @@ import type { ActionTarget } from "./action-target";
|
|
|
3
3
|
export type CustomButtonVariant = "solid" | "outline" | "ghost" | "link";
|
|
4
4
|
|
|
5
5
|
export interface CustomButton {
|
|
6
|
+
/** Set when a quick action can press the button. */
|
|
7
|
+
id?: string;
|
|
6
8
|
label: string;
|
|
7
9
|
icon?: string;
|
|
8
10
|
variant?: CustomButtonVariant;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { computed, type ComputedRef } from "vue";
|
|
1
2
|
import {
|
|
2
3
|
TREE_ARROW_DOWN_METADATA,
|
|
3
4
|
createTreeArrowDownShortcut,
|
|
@@ -11,7 +12,11 @@ import { TREE_SPACE_METADATA, createTreeSpaceShortcut } from "./treeSpace";
|
|
|
11
12
|
import { TREE_HOME_METADATA, createTreeHomeShortcut } from "./treeHome";
|
|
12
13
|
import { TREE_END_METADATA, createTreeEndShortcut } from "./treeEnd";
|
|
13
14
|
import { TREE_SHIFT_A_METADATA, createTreeShiftAShortcut } from "./treeShiftA";
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
TREE_ESCAPE_METADATA,
|
|
17
|
+
createTreeEscapeShortcut,
|
|
18
|
+
isTreeEscapeActive,
|
|
19
|
+
} from "./treeEscape";
|
|
15
20
|
|
|
16
21
|
export const TREE_SHORTCUTS_METADATA = [
|
|
17
22
|
TREE_ARROW_DOWN_METADATA,
|
|
@@ -29,15 +34,19 @@ interface BuildTreeShortcutsParams {
|
|
|
29
34
|
props: TreeProps;
|
|
30
35
|
items: ComputedRef<TreeNode[]>;
|
|
31
36
|
selected: Ref<TreeNode | TreeNode[] | undefined>;
|
|
37
|
+
activeElement: Ref<Element | null | undefined>;
|
|
32
38
|
}
|
|
33
39
|
|
|
40
|
+
type TreeShortcuts = Record<string, (() => void) | undefined>;
|
|
41
|
+
|
|
34
42
|
export function buildTreeShortcuts({
|
|
35
43
|
navigateTree,
|
|
36
44
|
props,
|
|
37
45
|
items,
|
|
38
46
|
selected,
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
activeElement,
|
|
48
|
+
}: BuildTreeShortcutsParams): ComputedRef<TreeShortcuts> {
|
|
49
|
+
const shortcuts = {
|
|
41
50
|
arrowdown: createTreeArrowDownShortcut(navigateTree),
|
|
42
51
|
arrowup: createTreeArrowUpShortcut(navigateTree),
|
|
43
52
|
enter: createTreeEnterShortcut(navigateTree),
|
|
@@ -45,6 +54,12 @@ export function buildTreeShortcuts({
|
|
|
45
54
|
home: createTreeHomeShortcut(navigateTree),
|
|
46
55
|
end: createTreeEndShortcut(navigateTree),
|
|
47
56
|
shift_a: createTreeShiftAShortcut(props, items, selected),
|
|
48
|
-
escape: createTreeEscapeShortcut(props, selected),
|
|
49
57
|
};
|
|
58
|
+
const escape = createTreeEscapeShortcut(props, selected);
|
|
59
|
+
// An unregistered key is left untouched by `defineShortcuts`, so Escape
|
|
60
|
+
// reaches overlays whenever the tree has nothing to clear.
|
|
61
|
+
return computed(() => ({
|
|
62
|
+
...shortcuts,
|
|
63
|
+
escape: isTreeEscapeActive(selected, activeElement) ? escape : undefined,
|
|
64
|
+
}));
|
|
50
65
|
}
|
|
@@ -1,9 +1,30 @@
|
|
|
1
|
+
import { isFocusInsideOverlay } from "../../../utils/overlayFocus";
|
|
2
|
+
|
|
1
3
|
export const TREE_ESCAPE_METADATA: ShortcutMetadata = {
|
|
2
4
|
key: ["$keyboard.escape"],
|
|
3
5
|
descriptionKey: "$dms.shortcuts.tree.escape.description",
|
|
4
6
|
component: "$dms.components.tree",
|
|
7
|
+
condition: {
|
|
8
|
+
descriptionKey: "$dms.shortcuts.tree.escape.condition",
|
|
9
|
+
},
|
|
5
10
|
};
|
|
6
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Nuxt UI's `defineShortcuts` calls `preventDefault()` on every key it
|
|
14
|
+
* handles, and reka-ui's DismissableLayer ignores a prevented Escape. The
|
|
15
|
+
* shortcut is therefore only registered when there is a selection to clear,
|
|
16
|
+
* and never while focus sits inside an overlay, so the overlay above the tree
|
|
17
|
+
* keeps closing on Escape.
|
|
18
|
+
*/
|
|
19
|
+
export function isTreeEscapeActive(
|
|
20
|
+
selected: Ref<TreeNode | TreeNode[] | undefined>,
|
|
21
|
+
activeElement: Ref<Element | null | undefined>,
|
|
22
|
+
): boolean {
|
|
23
|
+
if (isFocusInsideOverlay(activeElement.value)) return false;
|
|
24
|
+
const value = selected.value;
|
|
25
|
+
return Array.isArray(value) ? value.length > 0 : value != null;
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
export function createTreeEscapeShortcut(
|
|
8
29
|
props: TreeProps,
|
|
9
30
|
selected: Ref<TreeNode | TreeNode[] | undefined>,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* How an `openForm` quick action reaches its table view: it routes
|
|
3
|
-
* action's page with these query parameters, and the table view opens
|
|
4
|
-
* creation form when it mounts. Carried by the URL rather than a window event
|
|
2
|
+
* How an `openForm` or `button` quick action reaches its table view: it routes
|
|
3
|
+
* to the action's page with these query parameters, and the table view opens
|
|
4
|
+
* its creation form, or presses the named button, when it mounts. Carried by the URL rather than a window event
|
|
5
5
|
* so the action works from any page — the listener would otherwise have to
|
|
6
6
|
* exist before the navigation that creates it.
|
|
7
7
|
*/
|
|
@@ -9,3 +9,38 @@ export const QUICK_ACTION_QUERY_KEY = "quickAction";
|
|
|
9
9
|
export const QUICK_ACTION_ADD = "add";
|
|
10
10
|
/** Which table view answers, when the page mounts more than one. */
|
|
11
11
|
export const QUICK_ACTION_COMPONENT_KEY = "quickActionComponent";
|
|
12
|
+
export const QUICK_ACTION_BUTTON = "button";
|
|
13
|
+
/** Which of the table view's custom buttons a `button` quick action presses. */
|
|
14
|
+
export const QUICK_ACTION_BUTTON_KEY = "quickActionButton";
|
|
15
|
+
|
|
16
|
+
export type QuickActionIntent =
|
|
17
|
+
| { kind: "add" }
|
|
18
|
+
| { kind: "button"; button: string };
|
|
19
|
+
|
|
20
|
+
type QuickActionQuery = Record<string, unknown>;
|
|
21
|
+
|
|
22
|
+
const intentReaders: Record<
|
|
23
|
+
string,
|
|
24
|
+
(query: QuickActionQuery) => QuickActionIntent | undefined
|
|
25
|
+
> = {
|
|
26
|
+
[QUICK_ACTION_ADD]: () => ({ kind: "add" }),
|
|
27
|
+
[QUICK_ACTION_BUTTON]: (query) => {
|
|
28
|
+
const button = query[QUICK_ACTION_BUTTON_KEY];
|
|
29
|
+
return typeof button === "string" ? { kind: "button", button } : undefined;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* What the route asks the table view `componentId` to run. A page may mount
|
|
35
|
+
* several table views, so the quick action always names the one it means —
|
|
36
|
+
* resolved server-side — and only that one answers.
|
|
37
|
+
*/
|
|
38
|
+
export function readQuickActionIntent(
|
|
39
|
+
query: QuickActionQuery,
|
|
40
|
+
componentId: string | undefined,
|
|
41
|
+
): QuickActionIntent | undefined {
|
|
42
|
+
if (query[QUICK_ACTION_COMPONENT_KEY] !== componentId) return undefined;
|
|
43
|
+
const action = query[QUICK_ACTION_QUERY_KEY];
|
|
44
|
+
if (typeof action !== "string") return undefined;
|
|
45
|
+
return intentReaders[action]?.(query);
|
|
46
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Overlays reka-ui dismisses on Escape (modals, slideovers, popovers, menus,
|
|
2
|
+
// selects). They move focus inside themselves while open.
|
|
3
|
+
const OVERLAY_SELECTOR =
|
|
4
|
+
'[role="dialog"], [role="alertdialog"], [role="menu"], [role="listbox"]';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Nuxt UI's `defineShortcuts` calls `preventDefault()` on every key it
|
|
8
|
+
* handles, and reka-ui's DismissableLayer ignores a prevented Escape. A
|
|
9
|
+
* component-level Escape shortcut must therefore stay unregistered while
|
|
10
|
+
* focus sits inside an overlay, so the overlay keeps closing on Escape.
|
|
11
|
+
*/
|
|
12
|
+
export function isFocusInsideOverlay(
|
|
13
|
+
activeElement: Element | null | undefined,
|
|
14
|
+
): boolean {
|
|
15
|
+
return Boolean(activeElement?.closest(OVERLAY_SELECTOR));
|
|
16
|
+
}
|
|
@@ -292,7 +292,8 @@
|
|
|
292
292
|
"description": "Refresh table data"
|
|
293
293
|
},
|
|
294
294
|
"escape": {
|
|
295
|
-
"description": "Close search or deselect rows"
|
|
295
|
+
"description": "Close search or deselect rows",
|
|
296
|
+
"condition": "Only when a search or a row selection is active and no dialog is open"
|
|
296
297
|
},
|
|
297
298
|
"shift_n": {
|
|
298
299
|
"description": "Create new row",
|
|
@@ -330,7 +331,8 @@
|
|
|
330
331
|
"description": "Go to last node"
|
|
331
332
|
},
|
|
332
333
|
"escape": {
|
|
333
|
-
"description": "Deselect all nodes"
|
|
334
|
+
"description": "Deselect all nodes",
|
|
335
|
+
"condition": "Only when nodes are selected and no dialog is open"
|
|
334
336
|
},
|
|
335
337
|
"shift_a": {
|
|
336
338
|
"description": "Select all nodes recursively",
|
|
@@ -293,7 +293,8 @@
|
|
|
293
293
|
"description": "Actualiser les données du tableau"
|
|
294
294
|
},
|
|
295
295
|
"escape": {
|
|
296
|
-
"description": "Fermer la recherche ou désélectionner les lignes"
|
|
296
|
+
"description": "Fermer la recherche ou désélectionner les lignes",
|
|
297
|
+
"condition": "Uniquement quand une recherche ou une sélection est active et qu'aucune fenêtre n'est ouverte"
|
|
297
298
|
},
|
|
298
299
|
"shift_n": {
|
|
299
300
|
"description": "Créer une nouvelle ligne",
|
|
@@ -331,7 +332,8 @@
|
|
|
331
332
|
"description": "Aller au dernier nœud"
|
|
332
333
|
},
|
|
333
334
|
"escape": {
|
|
334
|
-
"description": "Désélectionner tous les nœuds"
|
|
335
|
+
"description": "Désélectionner tous les nœuds",
|
|
336
|
+
"condition": "Uniquement quand des nœuds sont sélectionnés et qu'aucune fenêtre n'est ouverte"
|
|
335
337
|
},
|
|
336
338
|
"shift_a": {
|
|
337
339
|
"description": "Sélectionner tous les nœuds récursivement",
|
|
@@ -124,8 +124,8 @@ importers:
|
|
|
124
124
|
specifier: '>=1.9.1 <2'
|
|
125
125
|
version: 1.9.1(@types/node@24.8.1)
|
|
126
126
|
'@antelopejs/dms-frontend':
|
|
127
|
-
specifier: '>=0.2
|
|
128
|
-
version: 0.2
|
|
127
|
+
specifier: '>=0.3.2 <1.0.0'
|
|
128
|
+
version: 0.3.2(@antelopejs/core@1.9.1(@types/node@24.8.1))
|
|
129
129
|
'@eslint/js':
|
|
130
130
|
specifier: ^8.57.0
|
|
131
131
|
version: 8.57.0
|
|
@@ -200,8 +200,8 @@ packages:
|
|
|
200
200
|
resolution: {integrity: sha512-MPLJak0rPluV4lgsdsO5ba03sgTxIGSncajBcoaoppuppQmksdpaemojIHiVLJ4LiG0iiH21Roaed8XXy1XkYQ==}
|
|
201
201
|
hasBin: true
|
|
202
202
|
|
|
203
|
-
'@antelopejs/dms-frontend@0.2
|
|
204
|
-
resolution: {integrity: sha512-
|
|
203
|
+
'@antelopejs/dms-frontend@0.3.2':
|
|
204
|
+
resolution: {integrity: sha512-BNqLqW3UbfeOMnd4RkEynr/1mMWFhwB8dt1JbggaGte8/KrJQ3MQ5TOovnACMmrqAD7FxsMLBsNqjzGjX4O4ZA==}
|
|
205
205
|
engines: {node: '>=20.12.0'}
|
|
206
206
|
hasBin: true
|
|
207
207
|
peerDependencies:
|
|
@@ -4331,7 +4331,7 @@ snapshots:
|
|
|
4331
4331
|
transitivePeerDependencies:
|
|
4332
4332
|
- '@types/node'
|
|
4333
4333
|
|
|
4334
|
-
'@antelopejs/dms-frontend@0.2
|
|
4334
|
+
'@antelopejs/dms-frontend@0.3.2(@antelopejs/core@1.9.1(@types/node@24.8.1))':
|
|
4335
4335
|
dependencies:
|
|
4336
4336
|
boxen: 5.1.2
|
|
4337
4337
|
chalk: 4.1.2
|