@antelopejs/dms 0.7.5 → 0.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/implementations/dms-base/export-jobs.d.ts +4 -1
- package/dist/implementations/dms-base/export-jobs.js +10 -6
- package/dist/implementations/dms-base/export-jobs.js.map +1 -1
- package/dist/implementations/dms-base/search-route.js +20 -11
- package/dist/implementations/dms-base/search-route.js.map +1 -1
- package/dist/test/attachment-host/capabilities.d.ts +2 -0
- package/dist/test/attachment-host/capabilities.js +11 -0
- package/dist/test/attachment-host/capabilities.js.map +1 -1
- package/dist/test/integration/table-view-capabilities.test.js +23 -5
- package/dist/test/integration/table-view-capabilities.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/data-types/field-types.test.js +29 -0
- package/dist/test/unit/interfaces/dms-base/data-types/field-types.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/display-blocks-catalog.test.js +14 -0
- package/dist/test/unit/interfaces/dms-base/display-blocks-catalog.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/export-jobs-binding.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/export-jobs-binding.test.js +49 -0
- package/dist/test/unit/interfaces/dms-base/export-jobs-binding.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-hidden-columns.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-hidden-columns.test.js +105 -0
- package/dist/test/unit/interfaces/dms-base/table-view-hidden-columns.test.js.map +1 -0
- package/dist/test/unit/request-tenant-guards.test.js +38 -0
- package/dist/test/unit/request-tenant-guards.test.js.map +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/accounts.vue +9 -3
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +6 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +6 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/CommandPaletteAssistantAnswer.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +2 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageSkeleton.vue +13 -4
- package/frontend-vue/layers/dms-layout/app/build/components/layout/RolePreviewBar.vue +9 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +5 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePersonalInfo.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSummaryRow.vue +4 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RoleEditor.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityEmail.vue +4 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityPassword.vue +4 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +19 -4
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/theme/AppearanceDeveloper.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/theme/CssVariablesList.vue +6 -3
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +20 -5
- package/frontend-vue/layers/dms-ui/app/build/components/chart/internal/ChartSkeleton.vue +4 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +10 -2
- package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +7 -2
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +87 -40
- package/frontend-vue/layers/dms-ui/app/build/components/skeleton/RowSkeleton.vue +8 -1
- package/frontend-vue/layers/dms-ui/app/build/components/stat/StatCell.vue +54 -13
- package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +9 -2
- package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +8 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +1 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +1 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +22 -5
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/ExpandedRowDetail.vue +1 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +3 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +3 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +25 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +20 -2
- package/frontend-vue/layers/dms-ui/app/build/utils/characterCount.ts +8 -0
- package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/blocks/CodeBlock.vue +3 -0
- package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +13 -5
- package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +7 -1
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +10 -3
- package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +8 -0
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +4 -3
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +6 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +5 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +27 -3
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +24 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/RichText.vue +13 -4
- package/frontend-vue/layers/dms-ui/app/components/form/components/Textarea.vue +27 -2
- package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +8 -4
- package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +12 -4
- package/frontend-vue/layers/dms-ui/app/components/meter/MeterBlock.vue +15 -4
- package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +69 -42
- package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +15 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +13 -3
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +45 -9
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/composables/tab/types/props.ts +5 -0
- package/frontend-vue/tests/confirm-dialog-fields.test.ts +12 -0
- package/frontend-vue/tests/drawer-target-direction.test.ts +1 -0
- package/frontend-vue/tests/field-suffix-count.test.ts +120 -0
- package/frontend-vue/tests/form-section-nav-groups.test.ts +116 -0
- package/frontend-vue/tests/nav-card-badge.test.ts +5 -1
- package/frontend-vue/tests/nuxt-ui-aria-labels.test.ts +58 -0
- package/frontend-vue/tests/page-fill-height.test.ts +2 -2
- package/frontend-vue/tests/save-bar.test.ts +49 -0
- package/frontend-vue/tests/stat-group-detail-lines.test.ts +63 -0
- package/frontend-vue/tests/tab-fit.test.ts +99 -0
- package/frontend-vue/tests/table-cell-sublines.test.ts +15 -0
- package/frontend-vue/tests/table-cell-wrap.test.ts +24 -2
- package/frontend-vue/tests/table-row-action-errors.test.ts +26 -0
- package/frontend-vue/tests/top-list-card-actions.test.ts +91 -0
- package/package.json +2 -2
- package/skills/dms/SKILL.md +11 -1
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { computed, useId } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
type FormSectionLayout,
|
|
5
|
+
type FormSectionNavGroup,
|
|
6
|
+
type FormSectionState,
|
|
7
|
+
groupSectionsForNav,
|
|
5
8
|
} from "../../composables/form/formSections";
|
|
9
|
+
import DmsEyebrow from "../../../components/section-header/Eyebrow.vue";
|
|
6
10
|
|
|
7
11
|
type FormSectionNavVariant = "list" | "chips";
|
|
8
12
|
|
|
@@ -21,6 +25,18 @@ const emit = defineEmits<{ select: [sectionId: string] }>();
|
|
|
21
25
|
const { t } = useI18n();
|
|
22
26
|
const { processI18n } = useTranslation();
|
|
23
27
|
|
|
28
|
+
// The side list reads its sections in titled runs (`navGroup`); the row of
|
|
29
|
+
// chips has no room for headings and lists them all in one run.
|
|
30
|
+
const GROUPS_CLASSES: Record<FormSectionNavVariant, string> = {
|
|
31
|
+
list: "flex flex-col gap-4",
|
|
32
|
+
chips: "",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const GROUP_CLASSES: Record<FormSectionNavVariant, string> = {
|
|
36
|
+
list: "grid gap-1",
|
|
37
|
+
chips: "contents",
|
|
38
|
+
};
|
|
39
|
+
|
|
24
40
|
const VARIANT_CLASSES: Record<FormSectionNavVariant, string> = {
|
|
25
41
|
list: "flex flex-col gap-0.5",
|
|
26
42
|
chips: "flex gap-1.5 overflow-x-auto pb-1",
|
|
@@ -37,6 +53,17 @@ const ACTIVE_CLASSES: Record<FormSectionNavVariant, string> = {
|
|
|
37
53
|
chips: "border-accented bg-elevated text-highlighted",
|
|
38
54
|
};
|
|
39
55
|
|
|
56
|
+
const groupIdPrefix = useId();
|
|
57
|
+
const groups = computed<FormSectionNavGroup[]>(() =>
|
|
58
|
+
props.variant === "list"
|
|
59
|
+
? groupSectionsForNav(props.sections)
|
|
60
|
+
: [{ sections: props.sections }],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
function groupHeadingId(index: number): string {
|
|
64
|
+
return `${groupIdPrefix}-group-${index}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
40
67
|
function stateLabel(state: FormSectionState | undefined): string | undefined {
|
|
41
68
|
if (state?.invalidCount) {
|
|
42
69
|
const count = state.invalidCount;
|
|
@@ -52,43 +79,63 @@ function onSelect(event: MouseEvent, sectionId: string): void {
|
|
|
52
79
|
</script>
|
|
53
80
|
|
|
54
81
|
<template>
|
|
55
|
-
<nav
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
<nav
|
|
83
|
+
:aria-label="t('dms.form.sections.nav')"
|
|
84
|
+
:class="GROUPS_CLASSES[props.variant]"
|
|
85
|
+
>
|
|
86
|
+
<div
|
|
87
|
+
v-for="(group, groupIndex) in groups"
|
|
88
|
+
:key="group.sections[0]?.id ?? groupIndex"
|
|
89
|
+
:class="GROUP_CLASSES[props.variant]"
|
|
90
|
+
>
|
|
91
|
+
<DmsEyebrow
|
|
92
|
+
v-if="group.label"
|
|
93
|
+
:id="groupHeadingId(groupIndex)"
|
|
94
|
+
:label="processI18n(group.label)"
|
|
95
|
+
class="px-2.5"
|
|
96
|
+
/>
|
|
97
|
+
<ul
|
|
98
|
+
:class="VARIANT_CLASSES[props.variant]"
|
|
99
|
+
:aria-labelledby="group.label ? groupHeadingId(groupIndex) : undefined"
|
|
100
|
+
>
|
|
101
|
+
<li v-for="section in group.sections" :key="section.id">
|
|
102
|
+
<a
|
|
103
|
+
:href="`#${props.anchorId(section.id)}`"
|
|
104
|
+
:class="[
|
|
105
|
+
ITEM_CLASSES[props.variant],
|
|
106
|
+
section.id === props.activeId && ACTIVE_CLASSES[props.variant],
|
|
107
|
+
]"
|
|
108
|
+
:aria-current="
|
|
109
|
+
section.id === props.activeId ? 'location' : undefined
|
|
110
|
+
"
|
|
111
|
+
@click="onSelect($event, section.id)"
|
|
78
112
|
>
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class="
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
113
|
+
<UIcon
|
|
114
|
+
v-if="section.icon && props.variant === 'list'"
|
|
115
|
+
:name="section.icon"
|
|
116
|
+
class="size-4 shrink-0"
|
|
117
|
+
/>
|
|
118
|
+
<span class="truncate">{{ processI18n(section.label) }}</span>
|
|
119
|
+
<!-- Read by its sr-only label below, not twice. -->
|
|
120
|
+
<span
|
|
121
|
+
v-if="props.states[section.id]?.invalidCount"
|
|
122
|
+
class="text-error ms-auto inline-flex items-center gap-1 font-mono text-[11px]"
|
|
123
|
+
aria-hidden="true"
|
|
124
|
+
>
|
|
125
|
+
<span class="bg-error size-1.5 rounded-full" aria-hidden="true" />
|
|
126
|
+
{{ props.states[section.id]?.invalidCount }}
|
|
127
|
+
</span>
|
|
128
|
+
<span
|
|
129
|
+
v-else-if="props.states[section.id]?.dirty"
|
|
130
|
+
class="bg-warning ms-auto size-1.5 shrink-0 rounded-full"
|
|
131
|
+
aria-hidden="true"
|
|
132
|
+
/>
|
|
133
|
+
<span v-if="stateLabel(props.states[section.id])" class="sr-only">
|
|
134
|
+
{{ stateLabel(props.states[section.id]) }}
|
|
135
|
+
</span>
|
|
136
|
+
</a>
|
|
137
|
+
</li>
|
|
138
|
+
</ul>
|
|
139
|
+
</div>
|
|
93
140
|
</nav>
|
|
94
141
|
</template>
|
|
@@ -18,6 +18,8 @@ const props = withDefaults(defineProps<RowSkeletonProps>(), {
|
|
|
18
18
|
linesClass: "space-y-1.5",
|
|
19
19
|
isCentered: false,
|
|
20
20
|
});
|
|
21
|
+
|
|
22
|
+
const { t } = useI18n();
|
|
21
23
|
</script>
|
|
22
24
|
|
|
23
25
|
<template>
|
|
@@ -26,9 +28,14 @@ const props = withDefaults(defineProps<RowSkeletonProps>(), {
|
|
|
26
28
|
:class="props.isCentered ? 'items-center' : 'items-start'"
|
|
27
29
|
aria-hidden="true"
|
|
28
30
|
>
|
|
29
|
-
<USkeleton
|
|
31
|
+
<USkeleton
|
|
32
|
+
:aria-label="t('dms.a11y.loading')"
|
|
33
|
+
class="shrink-0"
|
|
34
|
+
:class="props.well"
|
|
35
|
+
/>
|
|
30
36
|
<div class="min-w-0 flex-1" :class="props.linesClass">
|
|
31
37
|
<USkeleton
|
|
38
|
+
:aria-label="t('dms.a11y.loading')"
|
|
32
39
|
v-for="(line, index) in props.lines"
|
|
33
40
|
:key="index"
|
|
34
41
|
:class="line"
|
|
@@ -26,7 +26,10 @@ interface StatCellProps {
|
|
|
26
26
|
/** Icon of the well; the cell has no well without one. */
|
|
27
27
|
icon?: string;
|
|
28
28
|
tone?: IconWellTone;
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* The line under the value; a longer one wraps to a second line before its
|
|
31
|
+
* ellipsis, the row taking the height of its tallest cell.
|
|
32
|
+
*/
|
|
30
33
|
detail?: string;
|
|
31
34
|
/** Tone of the detail line (`neutral` = muted text). */
|
|
32
35
|
detailTone?: Tone;
|
|
@@ -91,6 +94,8 @@ const cellClass = computed(() => {
|
|
|
91
94
|
];
|
|
92
95
|
});
|
|
93
96
|
const detailClass = computed(() => DMS_TONE_TEXT[props.detailTone]);
|
|
97
|
+
|
|
98
|
+
const { t } = useI18n();
|
|
94
99
|
</script>
|
|
95
100
|
|
|
96
101
|
<template>
|
|
@@ -101,6 +106,7 @@ const detailClass = computed(() => DMS_TONE_TEXT[props.detailTone]);
|
|
|
101
106
|
>
|
|
102
107
|
<template v-if="isMd">
|
|
103
108
|
<USkeleton
|
|
109
|
+
:aria-label="t('dms.a11y.loading')"
|
|
104
110
|
v-if="props.placeholder || (props.loading && props.icon)"
|
|
105
111
|
class="size-10 shrink-0 rounded-[10px]"
|
|
106
112
|
/>
|
|
@@ -113,9 +119,18 @@ const detailClass = computed(() => DMS_TONE_TEXT[props.detailTone]);
|
|
|
113
119
|
<!-- A placeholder keeps the loaded lines' boxes (eyebrow 13px, value
|
|
114
120
|
24px, detail 16px), so the card keeps its height when it lands. -->
|
|
115
121
|
<div v-if="props.placeholder" class="grid flex-1 gap-[3px]">
|
|
116
|
-
<USkeleton
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
<USkeleton
|
|
123
|
+
:aria-label="t('dms.a11y.loading')"
|
|
124
|
+
class="my-[1.5px] h-2.5 w-20"
|
|
125
|
+
/>
|
|
126
|
+
<USkeleton
|
|
127
|
+
:aria-label="t('dms.a11y.loading')"
|
|
128
|
+
class="my-px h-[22px] w-10"
|
|
129
|
+
/>
|
|
130
|
+
<USkeleton
|
|
131
|
+
:aria-label="t('dms.a11y.loading')"
|
|
132
|
+
class="my-0.5 h-3 w-24"
|
|
133
|
+
/>
|
|
119
134
|
</div>
|
|
120
135
|
<div v-else class="grid min-w-0 flex-1 gap-[3px]">
|
|
121
136
|
<DmsEyebrow
|
|
@@ -124,7 +139,11 @@ const detailClass = computed(() => DMS_TONE_TEXT[props.detailTone]);
|
|
|
124
139
|
:class="wraps"
|
|
125
140
|
:label="props.eyebrow"
|
|
126
141
|
/>
|
|
127
|
-
<USkeleton
|
|
142
|
+
<USkeleton
|
|
143
|
+
:aria-label="t('dms.a11y.loading')"
|
|
144
|
+
v-if="props.loading"
|
|
145
|
+
class="h-[22px] w-16"
|
|
146
|
+
/>
|
|
128
147
|
<p
|
|
129
148
|
v-else
|
|
130
149
|
class="text-highlighted text-[22px] leading-[1.1] font-[650] tracking-[-0.035em] tabular-nums transition-opacity"
|
|
@@ -134,8 +153,8 @@ const detailClass = computed(() => DMS_TONE_TEXT[props.detailTone]);
|
|
|
134
153
|
</p>
|
|
135
154
|
<p
|
|
136
155
|
v-if="props.detail && !props.loading"
|
|
137
|
-
class="
|
|
138
|
-
:class="
|
|
156
|
+
class="line-clamp-2 text-xs [overflow-wrap:anywhere]"
|
|
157
|
+
:class="detailClass"
|
|
139
158
|
>
|
|
140
159
|
{{ props.detail }}
|
|
141
160
|
</p>
|
|
@@ -145,6 +164,7 @@ const detailClass = computed(() => DMS_TONE_TEXT[props.detailTone]);
|
|
|
145
164
|
|
|
146
165
|
<template v-else>
|
|
147
166
|
<USkeleton
|
|
167
|
+
:aria-label="t('dms.a11y.loading')"
|
|
148
168
|
v-if="props.placeholder || (props.loading && props.icon)"
|
|
149
169
|
class="size-8 shrink-0 rounded-[9px]"
|
|
150
170
|
/>
|
|
@@ -157,13 +177,26 @@ const detailClass = computed(() => DMS_TONE_TEXT[props.detailTone]);
|
|
|
157
177
|
<!-- Each placeholder line keeps the loaded line's box (eyebrow 11px,
|
|
158
178
|
value 19px, detail 16px), so the group keeps its height. -->
|
|
159
179
|
<div v-if="props.placeholder" class="grid flex-1 gap-0.5">
|
|
160
|
-
<USkeleton
|
|
161
|
-
|
|
162
|
-
|
|
180
|
+
<USkeleton
|
|
181
|
+
:aria-label="t('dms.a11y.loading')"
|
|
182
|
+
class="my-px h-[9px] w-16"
|
|
183
|
+
/>
|
|
184
|
+
<USkeleton
|
|
185
|
+
:aria-label="t('dms.a11y.loading')"
|
|
186
|
+
class="my-[2.5px] h-3.5 w-24"
|
|
187
|
+
/>
|
|
188
|
+
<USkeleton
|
|
189
|
+
:aria-label="t('dms.a11y.loading')"
|
|
190
|
+
class="my-0.5 h-3 w-20"
|
|
191
|
+
/>
|
|
163
192
|
</div>
|
|
164
193
|
<span v-else class="grid min-w-0 gap-0.5">
|
|
165
194
|
<DmsEyebrow as="span" size="xs" :label="props.eyebrow" />
|
|
166
|
-
<USkeleton
|
|
195
|
+
<USkeleton
|
|
196
|
+
:aria-label="t('dms.a11y.loading')"
|
|
197
|
+
v-if="props.loading"
|
|
198
|
+
class="my-0.5 h-3.5 w-24"
|
|
199
|
+
/>
|
|
167
200
|
<b
|
|
168
201
|
v-else
|
|
169
202
|
class="text-highlighted text-sm leading-[1.35] font-semibold whitespace-nowrap tabular-nums transition-opacity"
|
|
@@ -172,8 +205,16 @@ const detailClass = computed(() => DMS_TONE_TEXT[props.detailTone]);
|
|
|
172
205
|
<slot name="value">{{ props.value }}</slot>
|
|
173
206
|
</b>
|
|
174
207
|
<template v-if="props.detail">
|
|
175
|
-
<USkeleton
|
|
176
|
-
|
|
208
|
+
<USkeleton
|
|
209
|
+
:aria-label="t('dms.a11y.loading')"
|
|
210
|
+
v-if="props.loading"
|
|
211
|
+
class="h-3 w-20"
|
|
212
|
+
/>
|
|
213
|
+
<small
|
|
214
|
+
v-else
|
|
215
|
+
class="line-clamp-2 text-xs [overflow-wrap:anywhere]"
|
|
216
|
+
:class="detailClass"
|
|
217
|
+
>
|
|
177
218
|
{{ props.detail }}
|
|
178
219
|
</small>
|
|
179
220
|
</template>
|
|
@@ -130,8 +130,14 @@ const uiTablePagination = computed(() => uiTablePaginationVariant());
|
|
|
130
130
|
aria-hidden="true"
|
|
131
131
|
:class="[uiTablePagination.root(), uiTablePagination.placeholderRoot()]"
|
|
132
132
|
>
|
|
133
|
-
<USkeleton
|
|
134
|
-
|
|
133
|
+
<USkeleton
|
|
134
|
+
:aria-label="t('dms.a11y.loading')"
|
|
135
|
+
:class="uiTablePagination.placeholderCount()"
|
|
136
|
+
/>
|
|
137
|
+
<USkeleton
|
|
138
|
+
:aria-label="t('dms.a11y.loading')"
|
|
139
|
+
:class="uiTablePagination.placeholderPager()"
|
|
140
|
+
/>
|
|
135
141
|
</div>
|
|
136
142
|
<nav
|
|
137
143
|
v-else-if="rowCount"
|
|
@@ -183,6 +189,7 @@ const uiTablePagination = computed(() => uiTablePaginationVariant());
|
|
|
183
189
|
</span>
|
|
184
190
|
{{ summary.label }}
|
|
185
191
|
<USkeleton
|
|
192
|
+
:aria-label="t('dms.a11y.loading')"
|
|
186
193
|
v-if="summary.value === undefined"
|
|
187
194
|
aria-hidden="true"
|
|
188
195
|
:class="uiTablePagination.summaryPlaceholder()"
|
|
@@ -534,8 +534,12 @@ const theme = tv({
|
|
|
534
534
|
handle: REORDER_HANDLE_CLASS,
|
|
535
535
|
},
|
|
536
536
|
variants: {
|
|
537
|
+
// A wrapped cell grows its row past the 44px band: it keeps the band's
|
|
538
|
+
// breathing room above and below its lines instead of touching the
|
|
539
|
+
// hairlines (one line still fits the band, so its row keeps its height).
|
|
537
540
|
cellWrap: {
|
|
538
541
|
true: {
|
|
542
|
+
rowCell: "py-2.5",
|
|
539
543
|
rowSpan: "line-clamp-none whitespace-normal [overflow-wrap:anywhere]",
|
|
540
544
|
},
|
|
541
545
|
},
|
|
@@ -1249,6 +1253,7 @@ defineShortcuts({
|
|
|
1249
1253
|
{{ caption }}
|
|
1250
1254
|
</span>
|
|
1251
1255
|
<USkeleton
|
|
1256
|
+
:aria-label="t('dms.a11y.loading')"
|
|
1252
1257
|
v-if="isFirstPageLoading"
|
|
1253
1258
|
aria-hidden="true"
|
|
1254
1259
|
:class="uiTable.captionCountPlaceholder()"
|
|
@@ -1516,6 +1521,8 @@ defineShortcuts({
|
|
|
1516
1521
|
pinned: getPinnedVariant(cell.column),
|
|
1517
1522
|
loading,
|
|
1518
1523
|
rowClickable: isRowClickable(row.original),
|
|
1524
|
+
cellWrap: (cell.column.columnDef as TableColumn<T>)
|
|
1525
|
+
.cellWrap,
|
|
1519
1526
|
})
|
|
1520
1527
|
"
|
|
1521
1528
|
:style="{
|
|
@@ -1590,6 +1597,7 @@ defineShortcuts({
|
|
|
1590
1597
|
:class="uiTable.rowCell()"
|
|
1591
1598
|
>
|
|
1592
1599
|
<USkeleton
|
|
1600
|
+
:aria-label="t('dms.a11y.loading')"
|
|
1593
1601
|
v-if="column.columnDef.meta"
|
|
1594
1602
|
:class="uiTable.skeletonCell()"
|
|
1595
1603
|
:style="{ width: skeletonWidth(rowIndex, columnIndex) }"
|
|
@@ -21,6 +21,8 @@ const MAX_CARD_COUNT = 12;
|
|
|
21
21
|
const cardCount = computed(() =>
|
|
22
22
|
Math.max(1, Math.min(props.count, MAX_CARD_COUNT)),
|
|
23
23
|
);
|
|
24
|
+
|
|
25
|
+
const { t } = useI18n();
|
|
24
26
|
</script>
|
|
25
27
|
|
|
26
28
|
<template>
|
|
@@ -34,13 +36,22 @@ const cardCount = computed(() =>
|
|
|
34
36
|
class="border-default @container rounded-[10px] border bg-(--ui-bg) p-3.5 text-[12.5px]"
|
|
35
37
|
>
|
|
36
38
|
<div class="flex items-center gap-2.5">
|
|
37
|
-
<USkeleton
|
|
39
|
+
<USkeleton
|
|
40
|
+
:aria-label="t('dms.a11y.loading')"
|
|
41
|
+
class="size-7 shrink-0 rounded-[7px]"
|
|
42
|
+
/>
|
|
38
43
|
<div class="min-w-0 flex-1">
|
|
39
44
|
<div class="text-[13px]">
|
|
40
|
-
<USkeleton
|
|
45
|
+
<USkeleton
|
|
46
|
+
:aria-label="t('dms.a11y.loading')"
|
|
47
|
+
class="inline-block h-3 w-3/5 align-middle"
|
|
48
|
+
/>
|
|
41
49
|
</div>
|
|
42
50
|
<div class="font-mono text-[11px]">
|
|
43
|
-
<USkeleton
|
|
51
|
+
<USkeleton
|
|
52
|
+
:aria-label="t('dms.a11y.loading')"
|
|
53
|
+
class="inline-block h-2 w-2/5 align-middle"
|
|
54
|
+
/>
|
|
44
55
|
</div>
|
|
45
56
|
</div>
|
|
46
57
|
</div>
|
|
@@ -49,10 +60,16 @@ const cardCount = computed(() =>
|
|
|
49
60
|
>
|
|
50
61
|
<div v-for="field in props.fields" :key="field" class="min-w-0">
|
|
51
62
|
<div :class="['font-mono', props.labelClass]">
|
|
52
|
-
<USkeleton
|
|
63
|
+
<USkeleton
|
|
64
|
+
:aria-label="t('dms.a11y.loading')"
|
|
65
|
+
class="inline-block h-2 w-1/2 align-middle"
|
|
66
|
+
/>
|
|
53
67
|
</div>
|
|
54
68
|
<div class="mt-0.5">
|
|
55
|
-
<USkeleton
|
|
69
|
+
<USkeleton
|
|
70
|
+
:aria-label="t('dms.a11y.loading')"
|
|
71
|
+
class="inline-block h-2.5 w-4/5 align-middle"
|
|
72
|
+
/>
|
|
56
73
|
</div>
|
|
57
74
|
</div>
|
|
58
75
|
</div>
|
|
@@ -386,6 +386,8 @@ interface IdentityBadgeOption {
|
|
|
386
386
|
interface IdentityOptions {
|
|
387
387
|
avatarField?: string;
|
|
388
388
|
icon?: string;
|
|
389
|
+
/** Row field holding the tile's icon; `icon` when it is empty. */
|
|
390
|
+
iconField?: string;
|
|
389
391
|
subtitleField?: string;
|
|
390
392
|
subtitleTone?: string;
|
|
391
393
|
selfField?: string;
|
|
@@ -431,7 +433,7 @@ function renderIdentity(value: unknown, options: unknown, row: Row) {
|
|
|
431
433
|
| { key: string }
|
|
432
434
|
| string
|
|
433
435
|
| null,
|
|
434
|
-
icon: opts.icon,
|
|
436
|
+
icon: stringOf(readRowField(row, opts.iconField)) ?? opts.icon,
|
|
435
437
|
selfId: stringOf(readRowField(row, opts.selfField)),
|
|
436
438
|
selfLabel: opts.selfLabel ? processI18n(opts.selfLabel) : undefined,
|
|
437
439
|
badges,
|
|
@@ -51,8 +51,9 @@ export interface SaveBarState {
|
|
|
51
51
|
/** The submit shows but waits for a change. */
|
|
52
52
|
isSubmitHeld: boolean;
|
|
53
53
|
/**
|
|
54
|
-
* Nothing to offer: the bar
|
|
55
|
-
*
|
|
54
|
+
* Nothing to offer: the bar hides. The floating bar takes no space then
|
|
55
|
+
* (it overlays the form), a band keeps its place; either way its showing
|
|
56
|
+
* up never moves the content above it.
|
|
56
57
|
*/
|
|
57
58
|
isHidden: boolean;
|
|
58
59
|
}
|
|
@@ -21,6 +21,31 @@ export interface FormSectionsLayout {
|
|
|
21
21
|
sections: FormSectionLayout[];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** A run of the side navigation's entries under one heading, or none. */
|
|
25
|
+
export interface FormSectionNavGroup {
|
|
26
|
+
label?: string;
|
|
27
|
+
sections: FormSectionLayout[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The side navigation's entries, in order, cut wherever the heading changes:
|
|
32
|
+
* consecutive sections naming the same `navGroup` share one heading.
|
|
33
|
+
*/
|
|
34
|
+
export function groupSectionsForNav(
|
|
35
|
+
sections: readonly FormSectionLayout[],
|
|
36
|
+
): FormSectionNavGroup[] {
|
|
37
|
+
const groups: FormSectionNavGroup[] = [];
|
|
38
|
+
for (const section of sections) {
|
|
39
|
+
const current = groups.at(-1);
|
|
40
|
+
if (current && current.label === section.navGroup) {
|
|
41
|
+
current.sections.push(section);
|
|
42
|
+
} else {
|
|
43
|
+
groups.push({ label: section.navGroup, sections: [section] });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return groups;
|
|
47
|
+
}
|
|
48
|
+
|
|
24
49
|
/** An error of the form, as UForm lists them: the name it went under. */
|
|
25
50
|
export interface FormErrorRef {
|
|
26
51
|
name?: string;
|
package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts
CHANGED
|
@@ -652,6 +652,25 @@ export const useTableRowActions = <T extends Data>(
|
|
|
652
652
|
};
|
|
653
653
|
};
|
|
654
654
|
|
|
655
|
+
const findShownRow = (id: string | undefined): Data | undefined =>
|
|
656
|
+
config.rowNavigation
|
|
657
|
+
?.rows()
|
|
658
|
+
.find(
|
|
659
|
+
(row) =>
|
|
660
|
+
String(getItemId(row, config.rowIdKey ?? DEFAULT_ROW_ID_KEY)) === id,
|
|
661
|
+
);
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* What a fixed dialog words: the rows' count, and for one row shown in the
|
|
665
|
+
* table its own fields too (`{name}`). The count wins a clash, it is what
|
|
666
|
+
* every bulk dialog may name.
|
|
667
|
+
*/
|
|
668
|
+
const confirmRowFields = (ids: string[]): Data => {
|
|
669
|
+
const count = { count: ids.length };
|
|
670
|
+
const row = ids.length === 1 ? findShownRow(ids[0]) : undefined;
|
|
671
|
+
return row ? { ...row, ...count } : count;
|
|
672
|
+
};
|
|
673
|
+
|
|
655
674
|
/** Asks the declared confirmation of a bulk action, running it inside. */
|
|
656
675
|
const askDeclaredBulkConfirm = (
|
|
657
676
|
declared: ActionConfirm,
|
|
@@ -664,8 +683,7 @@ export const useTableRowActions = <T extends Data>(
|
|
|
664
683
|
id: ids[0],
|
|
665
684
|
[config.rowIdKey ?? DEFAULT_ROW_ID_KEY]: ids[0],
|
|
666
685
|
},
|
|
667
|
-
|
|
668
|
-
row: { count: ids.length },
|
|
686
|
+
row: confirmRowFields(ids),
|
|
669
687
|
fallback,
|
|
670
688
|
run,
|
|
671
689
|
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The counter a text field shows under `showCount`: the characters typed,
|
|
3
|
+
* out of `maxLength` when the field has one ("4 / 60").
|
|
4
|
+
*/
|
|
5
|
+
export function characterCountText(value: unknown, maxLength?: number): string {
|
|
6
|
+
const count = typeof value === "string" ? [...value].length : 0;
|
|
7
|
+
return maxLength === undefined ? String(count) : `${count} / ${maxLength}`;
|
|
8
|
+
}
|
|
@@ -213,7 +213,10 @@ const wrapperProps = computed(() =>
|
|
|
213
213
|
v-if="props.groupByDay"
|
|
214
214
|
class="flex items-center gap-2.5 px-[18px] pt-3.5 pb-1.5 after:h-px after:flex-1 after:bg-(--ui-border-muted)"
|
|
215
215
|
>
|
|
216
|
-
<USkeleton
|
|
216
|
+
<USkeleton
|
|
217
|
+
:aria-label="t('dms.a11y.loading')"
|
|
218
|
+
class="my-[1.5px] h-2.5 w-24"
|
|
219
|
+
/>
|
|
217
220
|
</div>
|
|
218
221
|
<div class="divide-y divide-(--ui-border-muted)">
|
|
219
222
|
<DmsRowSkeleton
|
|
@@ -67,6 +67,8 @@ const showSkeleton = computed(() => isLoading.value && !data.value);
|
|
|
67
67
|
const hasError = computed(
|
|
68
68
|
() => !isLoading.value && !data.value && Boolean(error.value),
|
|
69
69
|
);
|
|
70
|
+
|
|
71
|
+
const { t } = useI18n();
|
|
70
72
|
</script>
|
|
71
73
|
|
|
72
74
|
<template>
|
|
@@ -77,6 +79,7 @@ const hasError = computed(
|
|
|
77
79
|
aria-busy="true"
|
|
78
80
|
>
|
|
79
81
|
<USkeleton
|
|
82
|
+
:aria-label="t('dms.a11y.loading')"
|
|
80
83
|
v-for="width in SKELETON_LINE_WIDTHS"
|
|
81
84
|
:key="width"
|
|
82
85
|
class="h-3"
|
|
@@ -146,6 +146,8 @@ const hasStates = computed(() => cards.value.some((card) => !!card.state));
|
|
|
146
146
|
const isLeftOut = computed(
|
|
147
147
|
() => !!props.categoryId && !props.empty && cards.value.length === 0,
|
|
148
148
|
);
|
|
149
|
+
|
|
150
|
+
const { t } = useI18n();
|
|
149
151
|
</script>
|
|
150
152
|
|
|
151
153
|
<template>
|
|
@@ -180,14 +182,20 @@ const isLeftOut = computed(
|
|
|
180
182
|
class="dms-card flex flex-col gap-2.5 p-[18px]"
|
|
181
183
|
>
|
|
182
184
|
<div class="flex items-center gap-3">
|
|
183
|
-
<USkeleton
|
|
184
|
-
|
|
185
|
+
<USkeleton
|
|
186
|
+
:aria-label="t('dms.a11y.loading')"
|
|
187
|
+
class="size-9 rounded-[10px]"
|
|
188
|
+
/>
|
|
189
|
+
<USkeleton :aria-label="t('dms.a11y.loading')" class="h-3.5 w-28" />
|
|
185
190
|
</div>
|
|
186
191
|
<div class="grid h-[39px] content-center gap-[7.5px]">
|
|
187
|
-
<USkeleton class="h-3 w-4/5" />
|
|
188
|
-
<USkeleton class="h-3 w-3/5" />
|
|
192
|
+
<USkeleton :aria-label="t('dms.a11y.loading')" class="h-3 w-4/5" />
|
|
193
|
+
<USkeleton :aria-label="t('dms.a11y.loading')" class="h-3 w-3/5" />
|
|
189
194
|
</div>
|
|
190
|
-
<USkeleton
|
|
195
|
+
<USkeleton
|
|
196
|
+
:aria-label="t('dms.a11y.loading')"
|
|
197
|
+
class="my-[2.5px] h-3 w-28"
|
|
198
|
+
/>
|
|
191
199
|
</div>
|
|
192
200
|
</template>
|
|
193
201
|
<template v-else>
|
|
@@ -61,7 +61,10 @@ const attrs = useAttrs();
|
|
|
61
61
|
const theme = tv({
|
|
62
62
|
slots: {
|
|
63
63
|
root: "dms-card dms-card--interactive group flex flex-col gap-3 p-[18px] text-start",
|
|
64
|
-
|
|
64
|
+
// Two lines before the ellipsis: a title cut on its first line no longer
|
|
65
|
+
// says where the card leads, and the card already grows with its text.
|
|
66
|
+
title:
|
|
67
|
+
"text-highlighted line-clamp-2 min-w-0 text-sm font-[650] [overflow-wrap:anywhere]",
|
|
65
68
|
tag: `${MONO_TAG_CLASS} bg-elevated shrink-0 text-dimmed`,
|
|
66
69
|
badge: "shrink-0",
|
|
67
70
|
arrow:
|
|
@@ -78,6 +81,8 @@ const passthrough = computed(() => {
|
|
|
78
81
|
const { class: _class, ...rest } = attrs;
|
|
79
82
|
return rest;
|
|
80
83
|
});
|
|
84
|
+
|
|
85
|
+
const { t } = useI18n();
|
|
81
86
|
</script>
|
|
82
87
|
|
|
83
88
|
<template>
|
|
@@ -110,6 +115,7 @@ const passthrough = computed(() => {
|
|
|
110
115
|
</div>
|
|
111
116
|
|
|
112
117
|
<USkeleton
|
|
118
|
+
:aria-label="t('dms.a11y.loading')"
|
|
113
119
|
v-if="props.statePending && !props.state"
|
|
114
120
|
aria-hidden="true"
|
|
115
121
|
class="mt-auto h-[18px] w-28 self-start"
|