@conduction/nextcloud-vue 2.59.0 → 2.60.0
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/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +14 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +39 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +46 -4
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +224 -9
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +79 -25
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js +42 -3
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js +39 -6
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +46 -0
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +11 -4
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js +10 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js.map +1 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +39 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +16 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +117 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +147 -89
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +10 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +259 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +131 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +10 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +116 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +24 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +503 -28
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +300 -142
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js +10 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js +237 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js +120 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js +10 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +77 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js +27 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +169 -12
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +15 -7
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js +37 -3
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js +44 -16
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +132 -3
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -3
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js +10 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js +150 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js +106 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js +9 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +47 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js +17 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +49 -0
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js +6 -0
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js +46 -0
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js +15 -3
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +37 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +29 -13
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +50 -3
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js +10 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +186 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +46 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +11 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +298 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +77 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js +10 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js +169 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js +48 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js.map +1 -0
- package/dist/esm/components/CnTabs/CnTab.vue2.js +35 -0
- package/dist/esm/components/CnTabs/CnTab.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTab.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js +120 -7
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js +57 -5
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +59 -4
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +6 -2
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +10 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +484 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +115 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js +10 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js +189 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js +74 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js.map +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +82 -8
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +54 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +54 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +931 -4
- package/dist/esm/utils/dateVariant.js +124 -0
- package/dist/esm/utils/dateVariant.js.map +1 -0
- package/dist/esm/utils/detailActionModel.js +260 -0
- package/dist/esm/utils/detailActionModel.js.map +1 -0
- package/dist/esm/utils/dueRule.js +84 -0
- package/dist/esm/utils/dueRule.js.map +1 -0
- package/dist/esm/utils/fetchAggregate.js +37 -1
- package/dist/esm/utils/fetchAggregate.js.map +1 -1
- package/dist/esm/utils/fetchFilterCounts.js +186 -0
- package/dist/esm/utils/fetchFilterCounts.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +5 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/readPath.js +30 -0
- package/dist/esm/utils/readPath.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +14970 -8991
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +931 -4
- package/l10n/en.json +54 -1
- package/l10n/nl.json +54 -1
- package/package.json +1 -1
- package/scripts/.jsdoc-baselines.json +3 -0
- package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
- package/src/components/CnAppNav/CnAppNav.md +26 -0
- package/src/components/CnAppNav/CnAppNav.vue +104 -1
- package/src/components/CnBannerWidget/CnBannerWidget.vue +365 -10
- package/src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue +65 -3
- package/src/components/CnBoardView/CnBoardView.vue +77 -0
- package/src/components/CnBrandStripe/CnBrandStripe.md +22 -0
- package/src/components/CnBrandStripe/CnBrandStripe.vue +100 -0
- package/src/components/CnBrandStripe/index.js +4 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +207 -1
- package/src/components/CnConversationThread/CnConversationThread.md +51 -0
- package/src/components/CnConversationThread/CnConversationThread.vue +457 -0
- package/src/components/CnConversationThread/CnConversationWidget.vue +144 -0
- package/src/components/CnConversationThread/index.js +4 -0
- package/src/components/CnDetailPage/CnDetailPage.md +16 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +683 -48
- package/src/components/CnDocumentReviewList/CnDocumentReviewList.md +55 -0
- package/src/components/CnDocumentReviewList/CnDocumentReviewList.vue +384 -0
- package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +108 -0
- package/src/components/CnDocumentReviewList/index.js +4 -0
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +189 -14
- package/src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue +53 -3
- package/src/components/CnIndexPage/CnIndexPage.vue +135 -3
- package/src/components/CnNextStepCard/CnNextStepCard.md +40 -0
- package/src/components/CnNextStepCard/CnNextStepCard.vue +319 -0
- package/src/components/CnNextStepCard/CnNextStepWidget.vue +59 -0
- package/src/components/CnNextStepCard/index.js +4 -0
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +49 -0
- package/src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue +6 -0
- package/src/components/CnObjectKanban/CnObjectKanban.vue +86 -2
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +3 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +60 -1
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +50 -3
- package/src/components/CnSegmentedControl/CnSegmentedControl.md +46 -0
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +291 -0
- package/src/components/CnSegmentedControl/index.js +4 -0
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.md +15 -0
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +414 -0
- package/src/components/CnStackedBarWidget/index.js +14 -0
- package/src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue +224 -0
- package/src/components/CnStackedBarWidgetForm/index.js +4 -0
- package/src/components/CnTabs/CnTab.vue +35 -0
- package/src/components/CnTabs/CnTabs.vue +244 -10
- package/src/components/CnTabsWidget/CnTabsWidget.vue +63 -4
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.md +31 -0
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +668 -0
- package/src/components/CnWeekStripWidget/index.js +14 -0
- package/src/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue +265 -0
- package/src/components/CnWeekStripWidgetForm/index.js +4 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +74 -0
- package/src/components/index.js +11 -0
- package/src/css/detail-page.css +47 -0
- package/src/index.js +9 -0
- package/src/schemas/app-manifest-v2.schema.json +389 -1
- package/src/schemas/app-manifest.schema.json +2 -1
- package/src/utils/dateVariant.js +120 -0
- package/src/utils/detailActionModel.js +256 -0
- package/src/utils/dueRule.js +94 -0
- package/src/utils/fetchAggregate.js +36 -0
- package/src/utils/fetchFilterCounts.js +182 -0
- package/src/utils/index.js +1 -0
- package/src/utils/libraryWidgetKeys.js +5 -0
- package/src/utils/readPath.js +27 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="cn-tabs" :class="{ 'cn-tabs--card': card }">
|
|
2
|
+
<div class="cn-tabs" :class="{ 'cn-tabs--card': card, 'cn-tabs--segmented': variant === 'segmented' }">
|
|
3
3
|
<div class="cn-tabs__bar">
|
|
4
4
|
<div
|
|
5
5
|
class="cn-tabs__strip"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
@wheel="onNavWheel"
|
|
15
15
|
@scroll="measureOverflow">
|
|
16
16
|
<button
|
|
17
|
-
v-for="tab in
|
|
17
|
+
v-for="tab in visibleTabs"
|
|
18
18
|
:id="tab.tabId"
|
|
19
19
|
:key="tab.uid"
|
|
20
20
|
ref="navButtons"
|
|
@@ -27,7 +27,14 @@
|
|
|
27
27
|
:tabindex="isActive(tab.uid) ? 0 : -1"
|
|
28
28
|
:disabled="tab.disabled || null"
|
|
29
29
|
@click="tab.onActivate()">
|
|
30
|
-
|
|
30
|
+
<!-- The space before the count is load-bearing: it keeps
|
|
31
|
+
the tab's accessible name "Documents 5" rather than
|
|
32
|
+
"Documents5". The margin separates the two for the eye
|
|
33
|
+
only, so keep both tags on one line. -->
|
|
34
|
+
<component :is="tab.titleRender" /> <span
|
|
35
|
+
v-if="hasCount(tab)"
|
|
36
|
+
class="cn-tabs__count"
|
|
37
|
+
data-testid="cn-tabs-count">{{ tab.count }}</span>
|
|
31
38
|
</button>
|
|
32
39
|
</div>
|
|
33
40
|
<!-- Mouse-only affordances: keyboard users already have the arrow
|
|
@@ -51,6 +58,36 @@
|
|
|
51
58
|
<ChevronRight :size="20" />
|
|
52
59
|
</button>
|
|
53
60
|
</div>
|
|
61
|
+
<!-- The tabs that did not make the strip. OUTSIDE the tablist, like
|
|
62
|
+
`#nav-end`: a menu button nested in it would be announced as
|
|
63
|
+
one more tab. A tab picked here joins the strip while it is
|
|
64
|
+
selected, so the open panel always has a real tab to be
|
|
65
|
+
labelled by. -->
|
|
66
|
+
<div v-if="overflowTabs.length > 0" class="cn-tabs__more">
|
|
67
|
+
<NcActions
|
|
68
|
+
:forceMenu="true"
|
|
69
|
+
:forceName="true"
|
|
70
|
+
:menuName="moreLabel"
|
|
71
|
+
:aria-label="moreAriaLabel"
|
|
72
|
+
variant="tertiary"
|
|
73
|
+
data-testid="cn-tabs-more">
|
|
74
|
+
<template #icon>
|
|
75
|
+
<ChevronDown :size="20" />
|
|
76
|
+
</template>
|
|
77
|
+
<NcActionButton
|
|
78
|
+
v-for="tab in overflowTabs"
|
|
79
|
+
:key="tab.uid"
|
|
80
|
+
:disabled="tab.disabled || null"
|
|
81
|
+
:closeAfterClick="true"
|
|
82
|
+
data-testid="cn-tabs-more-item"
|
|
83
|
+
@click="onOverflowPick(tab)">
|
|
84
|
+
<template #icon>
|
|
85
|
+
<span class="cn-tabs__more-spacer" />
|
|
86
|
+
</template>
|
|
87
|
+
{{ overflowLabel(tab) }}
|
|
88
|
+
</NcActionButton>
|
|
89
|
+
</NcActions>
|
|
90
|
+
</div>
|
|
54
91
|
<div v-if="$slots['nav-end']" class="cn-tabs__nav-end">
|
|
55
92
|
<!-- @slot nav-end Rendered at the right-hand end of the tab bar, deliberately OUTSIDE the `role="tablist"` element. A widget Actions menu belongs beside the strip, not inside it: anything nested in the tablist is announced as one of the tabs, so a screen-reader user counting six tabs would hear seven. -->
|
|
56
93
|
<!-- @binding {number} active-index Index of the currently selected tab. -->
|
|
@@ -131,8 +168,27 @@
|
|
|
131
168
|
* wiring, a roving `tabindex` (only the selected tab is in the tab order), and
|
|
132
169
|
* Left/Right/Home/End keyboard navigation within the strip. Pass `aria-label`
|
|
133
170
|
* (or `ariaLabel`) so screen-reader users hear what the strip is for.
|
|
171
|
+
*
|
|
172
|
+
* ## Counts and the "More" menu
|
|
173
|
+
*
|
|
174
|
+
* A `CnTab` with `count` shows that number after its title. A `CnTab` with
|
|
175
|
+
* `overflow` is listed under a "More" menu beside the strip instead of in it:
|
|
176
|
+
*
|
|
177
|
+
* ```vue
|
|
178
|
+
* <CnTabs aria-label="Case details">
|
|
179
|
+
* <CnTab title="Documents" :count="5">…</CnTab>
|
|
180
|
+
* <CnTab title="Archiving" overflow>…</CnTab>
|
|
181
|
+
* </CnTabs>
|
|
182
|
+
* ```
|
|
183
|
+
*
|
|
184
|
+
* Picking a tab from the menu selects it and shows it in the strip for as long
|
|
185
|
+
* as it is selected. With no overflow tab the menu does not render, and the
|
|
186
|
+
* strip is exactly what it was before these props existed.
|
|
134
187
|
*/
|
|
135
|
-
import {
|
|
188
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
189
|
+
import { NcActionButton, NcActions } from '@nextcloud/vue'
|
|
190
|
+
import { computed, defineComponent, nextTick, onBeforeUnmount, onMounted, onUpdated, provide, reactive, ref, watch } from 'vue'
|
|
191
|
+
import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'
|
|
136
192
|
import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'
|
|
137
193
|
import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
|
|
138
194
|
import { CN_TABS_INJECTION_KEY } from './tabsKey.js'
|
|
@@ -141,8 +197,11 @@ export default defineComponent({
|
|
|
141
197
|
name: 'CnTabs',
|
|
142
198
|
|
|
143
199
|
components: {
|
|
200
|
+
ChevronDown,
|
|
144
201
|
ChevronLeft,
|
|
145
202
|
ChevronRight,
|
|
203
|
+
NcActionButton,
|
|
204
|
+
NcActions,
|
|
146
205
|
},
|
|
147
206
|
|
|
148
207
|
props: {
|
|
@@ -164,6 +223,20 @@ export default defineComponent({
|
|
|
164
223
|
default: false,
|
|
165
224
|
},
|
|
166
225
|
|
|
226
|
+
/**
|
|
227
|
+
* How the strip is drawn: `line` (the default: tabs joined to the
|
|
228
|
+
* panel below) or `segmented` (a compact pill switch, for a view
|
|
229
|
+
* switch such as "My work / My team"). Roles and keyboard behaviour
|
|
230
|
+
* are the same in both.
|
|
231
|
+
*
|
|
232
|
+
* @type {'line'|'segmented'}
|
|
233
|
+
*/
|
|
234
|
+
variant: {
|
|
235
|
+
type: String,
|
|
236
|
+
default: 'line',
|
|
237
|
+
validator: (v) => ['line', 'segmented'].includes(v),
|
|
238
|
+
},
|
|
239
|
+
|
|
167
240
|
/**
|
|
168
241
|
* Accessible name for the tab strip, applied to the `role="tablist"`
|
|
169
242
|
* element. Screen readers announce it when focus enters the strip.
|
|
@@ -172,6 +245,12 @@ export default defineComponent({
|
|
|
172
245
|
type: String,
|
|
173
246
|
default: '',
|
|
174
247
|
},
|
|
248
|
+
|
|
249
|
+
/** Name of the menu that lists the `overflow` tabs. */
|
|
250
|
+
moreLabel: {
|
|
251
|
+
type: String,
|
|
252
|
+
default: () => t('nextcloud-vue', 'More'),
|
|
253
|
+
},
|
|
175
254
|
},
|
|
176
255
|
|
|
177
256
|
emits: [
|
|
@@ -184,6 +263,10 @@ export default defineComponent({
|
|
|
184
263
|
const tabs = reactive([])
|
|
185
264
|
const activeUid = ref(null)
|
|
186
265
|
const navButtons = ref([])
|
|
266
|
+
const nav = ref(null)
|
|
267
|
+
// Whether the selection was chosen (a tab declared `active`, or the
|
|
268
|
+
// user picked one) rather than defaulted to the first tab registered.
|
|
269
|
+
let selectionChosen = false
|
|
187
270
|
|
|
188
271
|
/**
|
|
189
272
|
* Register a child tab. The first child to register wins the initial
|
|
@@ -199,8 +282,23 @@ export default defineComponent({
|
|
|
199
282
|
if (tab.disabled) {
|
|
200
283
|
return
|
|
201
284
|
}
|
|
202
|
-
if (
|
|
285
|
+
if (tab.active) {
|
|
203
286
|
activeUid.value = tab.uid
|
|
287
|
+
selectionChosen = true
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
if (activeUid.value === null) {
|
|
291
|
+
activeUid.value = tab.uid
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
// A tab under "More" only held the default selection because it
|
|
295
|
+
// registered first. The first tab that is actually in the strip
|
|
296
|
+
// takes it over, unless somebody chose.
|
|
297
|
+
if (!selectionChosen && !tab.overflow) {
|
|
298
|
+
const current = tabs.find((candidate) => candidate.uid === activeUid.value)
|
|
299
|
+
if (current && current.overflow) {
|
|
300
|
+
activeUid.value = tab.uid
|
|
301
|
+
}
|
|
204
302
|
}
|
|
205
303
|
}
|
|
206
304
|
|
|
@@ -232,6 +330,7 @@ export default defineComponent({
|
|
|
232
330
|
* @return {void}
|
|
233
331
|
*/
|
|
234
332
|
function select(uid) {
|
|
333
|
+
selectionChosen = true
|
|
235
334
|
if (activeUid.value === uid) {
|
|
236
335
|
return
|
|
237
336
|
}
|
|
@@ -252,6 +351,67 @@ export default defineComponent({
|
|
|
252
351
|
|
|
253
352
|
const activeIndex = computed(() => tabs.findIndex((tab) => tab.uid === activeUid.value))
|
|
254
353
|
|
|
354
|
+
/**
|
|
355
|
+
* The tabs drawn in the strip: every tab that is not `overflow`, plus
|
|
356
|
+
* an overflow tab for as long as it is the selected one.
|
|
357
|
+
*
|
|
358
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
359
|
+
*/
|
|
360
|
+
const visibleTabs = computed(() => tabs.filter((tab) => !tab.overflow || tab.uid === activeUid.value))
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* The tabs listed under "More": overflow tabs that are not selected.
|
|
364
|
+
*
|
|
365
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
366
|
+
*/
|
|
367
|
+
const overflowTabs = computed(() => tabs.filter((tab) => tab.overflow && tab.uid !== activeUid.value))
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Whether a tab declares a count. `0` is a count; null and '' are not.
|
|
371
|
+
*
|
|
372
|
+
* @param {object} tab Child descriptor.
|
|
373
|
+
*
|
|
374
|
+
* @return {boolean} True when the count renders.
|
|
375
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
376
|
+
*/
|
|
377
|
+
function hasCount(tab) {
|
|
378
|
+
return tab.count !== null && tab.count !== undefined && tab.count !== ''
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* A "More" entry's text: the tab's title, and its count when it has one.
|
|
383
|
+
*
|
|
384
|
+
* @param {object} tab Child descriptor.
|
|
385
|
+
*
|
|
386
|
+
* @return {string} The menu label.
|
|
387
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
388
|
+
*/
|
|
389
|
+
function overflowLabel(tab) {
|
|
390
|
+
return hasCount(tab) ? `${tab.label} (${tab.count})` : tab.label
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Select a tab picked from "More" and move focus to it in the strip,
|
|
395
|
+
* so a keyboard user lands on the tab they chose rather than on a menu
|
|
396
|
+
* button that no longer lists it.
|
|
397
|
+
*
|
|
398
|
+
* @param {object} tab Child descriptor.
|
|
399
|
+
*
|
|
400
|
+
* @return {void}
|
|
401
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
402
|
+
*/
|
|
403
|
+
function onOverflowPick(tab) {
|
|
404
|
+
tab.onActivate()
|
|
405
|
+
nextTick(() => {
|
|
406
|
+
const button = nav.value?.querySelector(`#${tab.tabId}`)
|
|
407
|
+
if (button && typeof button.focus === 'function') {
|
|
408
|
+
button.focus()
|
|
409
|
+
}
|
|
410
|
+
})
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const moreAriaLabel = computed(() => t('nextcloud-vue', 'More tabs'))
|
|
414
|
+
|
|
255
415
|
/**
|
|
256
416
|
* Move the selection and DOM focus to a tab by index, skipping disabled
|
|
257
417
|
* ones. Focus has to move with the selection: in the WAI-ARIA tabs
|
|
@@ -263,14 +423,16 @@ export default defineComponent({
|
|
|
263
423
|
* @return {void}
|
|
264
424
|
*/
|
|
265
425
|
function focusTab(index) {
|
|
266
|
-
|
|
426
|
+
// The strip's own tabs only: the arrow keys walk what is drawn, and
|
|
427
|
+
// the "More" menu has its own keyboard handling.
|
|
428
|
+
const enabled = visibleTabs.value.filter((tab) => !tab.disabled)
|
|
267
429
|
if (enabled.length === 0) {
|
|
268
430
|
return
|
|
269
431
|
}
|
|
270
432
|
const wrapped = ((index % enabled.length) + enabled.length) % enabled.length
|
|
271
433
|
const target = enabled[wrapped]
|
|
434
|
+
const position = visibleTabs.value.indexOf(target)
|
|
272
435
|
select(target.uid)
|
|
273
|
-
const position = tabs.indexOf(target)
|
|
274
436
|
const button = navButtons.value?.[position]
|
|
275
437
|
if (button && typeof button.focus === 'function') {
|
|
276
438
|
button.focus()
|
|
@@ -285,7 +447,7 @@ export default defineComponent({
|
|
|
285
447
|
* @return {void}
|
|
286
448
|
*/
|
|
287
449
|
function onNavKeydown(event) {
|
|
288
|
-
const enabled =
|
|
450
|
+
const enabled = visibleTabs.value.filter((tab) => !tab.disabled)
|
|
289
451
|
const current = enabled.findIndex((tab) => tab.uid === activeUid.value)
|
|
290
452
|
let next = null
|
|
291
453
|
|
|
@@ -306,8 +468,6 @@ export default defineComponent({
|
|
|
306
468
|
focusTab(next)
|
|
307
469
|
}
|
|
308
470
|
|
|
309
|
-
const nav = ref(null)
|
|
310
|
-
|
|
311
471
|
/**
|
|
312
472
|
* Scroll the strip so the selected tab is wholly in view.
|
|
313
473
|
*
|
|
@@ -402,6 +562,12 @@ export default defineComponent({
|
|
|
402
562
|
|
|
403
563
|
return {
|
|
404
564
|
tabs,
|
|
565
|
+
visibleTabs,
|
|
566
|
+
overflowTabs,
|
|
567
|
+
hasCount,
|
|
568
|
+
overflowLabel,
|
|
569
|
+
onOverflowPick,
|
|
570
|
+
moreAriaLabel,
|
|
405
571
|
isActive,
|
|
406
572
|
activeIndex,
|
|
407
573
|
nav,
|
|
@@ -534,6 +700,34 @@ export default defineComponent({
|
|
|
534
700
|
inset-inline-end: 0;
|
|
535
701
|
}
|
|
536
702
|
|
|
703
|
+
/* The count behind a tab's title. Full-contrast text on the page background,
|
|
704
|
+
so it reads on the idle tab's darker fill and on the open tab alike. */
|
|
705
|
+
.cn-tabs__count {
|
|
706
|
+
background-color: var(--color-main-background);
|
|
707
|
+
border: 1px solid var(--color-border-dark);
|
|
708
|
+
border-radius: var(--border-radius-pill, 100px);
|
|
709
|
+
color: var(--color-main-text);
|
|
710
|
+
display: inline-block;
|
|
711
|
+
font-size: 0.85em;
|
|
712
|
+
line-height: 1.4;
|
|
713
|
+
margin-inline-start: 6px;
|
|
714
|
+
min-width: 1.4em;
|
|
715
|
+
padding: 0 6px;
|
|
716
|
+
text-align: center;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.cn-tabs__more {
|
|
720
|
+
align-items: center;
|
|
721
|
+
display: flex;
|
|
722
|
+
flex: 0 0 auto;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/* Keeps a "More" entry's text on the menu's text column without drawing an icon. */
|
|
726
|
+
.cn-tabs__more-spacer {
|
|
727
|
+
display: inline-block;
|
|
728
|
+
width: 20px;
|
|
729
|
+
}
|
|
730
|
+
|
|
537
731
|
.cn-tabs__nav-end {
|
|
538
732
|
align-items: center;
|
|
539
733
|
display: flex;
|
|
@@ -645,4 +839,44 @@ export default defineComponent({
|
|
|
645
839
|
border-top: none;
|
|
646
840
|
padding: 12px;
|
|
647
841
|
}
|
|
842
|
+
|
|
843
|
+
/* Segmented variant: the strip is a pill switch that stands on its own, so
|
|
844
|
+
the bar's rule and the tab-joins-panel treatment are both dropped. */
|
|
845
|
+
.cn-tabs--segmented .cn-tabs__bar {
|
|
846
|
+
border-bottom: none;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
.cn-tabs--segmented .cn-tabs__strip {
|
|
850
|
+
flex: 0 1 auto;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
.cn-tabs--segmented .cn-tabs__nav {
|
|
854
|
+
gap: 4px;
|
|
855
|
+
margin-bottom: 0;
|
|
856
|
+
padding: 4px;
|
|
857
|
+
border-radius: var(--border-radius-container, var(--border-radius-large, 10px));
|
|
858
|
+
background-color: var(--color-background-dark);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
.cn-tabs--segmented .cn-tabs__nav-item {
|
|
862
|
+
border: 1px solid transparent;
|
|
863
|
+
border-radius: var(--border-radius-element, var(--border-radius-large, 8px));
|
|
864
|
+
background-color: transparent;
|
|
865
|
+
font-weight: 600;
|
|
866
|
+
padding: 6px 16px;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.cn-tabs--segmented .cn-tabs__nav-item:hover {
|
|
870
|
+
background-color: var(--color-background-hover);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/* The open segment is the raised one. Its border survives forced-colours
|
|
874
|
+
mode, which drops the background and the shadow. */
|
|
875
|
+
.cn-tabs--segmented .cn-tabs__nav-item--active {
|
|
876
|
+
border: 1px solid var(--color-border-dark);
|
|
877
|
+
background-color: var(--color-main-background);
|
|
878
|
+
box-shadow: 0 1px 2px var(--color-box-shadow);
|
|
879
|
+
font-weight: 600;
|
|
880
|
+
padding-top: 6px;
|
|
881
|
+
}
|
|
648
882
|
</style>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<div class="cn-tabs-widget">
|
|
8
8
|
<CnTabs
|
|
9
9
|
:aria-label="stripLabel"
|
|
10
|
+
:moreLabel="moreLabel"
|
|
10
11
|
class="cn-tabs-widget__tabs"
|
|
11
12
|
@update:activeIndex="onTabChange">
|
|
12
13
|
<!-- One Actions menu for the whole widget, bound to whichever child
|
|
@@ -55,6 +56,9 @@
|
|
|
55
56
|
v-for="(entry, index) in resolvedTabs"
|
|
56
57
|
:key="entry.key"
|
|
57
58
|
:active="index === activeIndex"
|
|
59
|
+
:title="entry.label"
|
|
60
|
+
:count="entry.count"
|
|
61
|
+
:overflow="entry.overflow"
|
|
58
62
|
lazy
|
|
59
63
|
@click="activeIndex = index">
|
|
60
64
|
<template #title>
|
|
@@ -97,6 +101,7 @@ import CnDetailWidgetHost from '../CnDetailWidgetHost/CnDetailWidgetHost.vue'
|
|
|
97
101
|
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
98
102
|
import CnTab from '../CnTabs/CnTab.vue'
|
|
99
103
|
import CnTabs from '../CnTabs/CnTabs.vue'
|
|
104
|
+
import { resolveTabCount } from '../../utils/detailActionModel.js'
|
|
100
105
|
import { PANEL_ACTION_SINK } from '../../utils/panelActions.js'
|
|
101
106
|
import { widgetTitleOf } from '../../utils/widgetDispatch.js'
|
|
102
107
|
import { CnActionsMenu } from '../CnActionsMenu/index.js'
|
|
@@ -233,10 +238,17 @@ export default {
|
|
|
233
238
|
/**
|
|
234
239
|
* The widget's config: `{ tabs, ariaLabel }`.
|
|
235
240
|
*
|
|
236
|
-
* `tabs[]` entries are `{ widgetId, label?, icon? }`.
|
|
237
|
-
* fall back to the referenced widget's own title and
|
|
241
|
+
* `tabs[]` entries are `{ widgetId, label?, icon?, count?, countField?, overflow? }`.
|
|
242
|
+
* `label` and `icon` fall back to the referenced widget's own title and
|
|
243
|
+
* icon. `count` is a number shown after the label; `countField` reads
|
|
244
|
+
* it off the record instead (a list counts its items). `overflow: true`
|
|
245
|
+
* lists the tab under "More".
|
|
238
246
|
*
|
|
239
|
-
*
|
|
247
|
+
* `maxVisibleTabs` caps the strip: later tabs go under "More".
|
|
248
|
+
* `hideEmpty: true` moves a tab whose count is 0 there too.
|
|
249
|
+
* `moreLabel` names that menu.
|
|
250
|
+
*
|
|
251
|
+
* @type {{ tabs?: Array<{widgetId: string, label?: string, icon?: string, count?: number, countField?: string, overflow?: boolean}>, ariaLabel?: string, maxVisibleTabs?: number, hideEmpty?: boolean, moreLabel?: string }}
|
|
240
252
|
*/
|
|
241
253
|
content: {
|
|
242
254
|
type: Object,
|
|
@@ -374,23 +386,56 @@ export default {
|
|
|
374
386
|
/**
|
|
375
387
|
* The configured tabs, each paired with the widget definition it names.
|
|
376
388
|
*
|
|
377
|
-
*
|
|
389
|
+
* A tab goes under the strip's "More" menu when it says `overflow`, when
|
|
390
|
+
* `content.hideEmpty` is set and its count is 0, or when
|
|
391
|
+
* `content.maxVisibleTabs` places are already taken. `count` is a
|
|
392
|
+
* literal, or read off the record through `countField`.
|
|
393
|
+
*
|
|
394
|
+
* @return {object[]} `{ key, widgetId, label, icon, widget, count, overflow }` per tab.
|
|
395
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
378
396
|
*/
|
|
379
397
|
resolvedTabs() {
|
|
380
398
|
const tabs = Array.isArray(this.content?.tabs) ? this.content.tabs : []
|
|
399
|
+
const max = Number(this.content?.maxVisibleTabs)
|
|
400
|
+
const hideEmpty = this.content?.hideEmpty === true
|
|
401
|
+
// How many tabs have taken a place in the strip so far. A tab that
|
|
402
|
+
// is already going under "More" does not use one up.
|
|
403
|
+
let placed = 0
|
|
381
404
|
return tabs.map((tab, index) => {
|
|
382
405
|
const widgetId = typeof tab === 'string' ? tab : tab?.widgetId
|
|
383
406
|
const widget = this.availableWidgets.find((w) => w && w.id === widgetId) || null
|
|
407
|
+
const count = resolveTabCount(typeof tab === 'object' ? tab : null, this.objectData)
|
|
408
|
+
let overflow = (tab && tab.overflow === true) || (hideEmpty && count === 0)
|
|
409
|
+
if (!overflow) {
|
|
410
|
+
if (Number.isFinite(max) && max > 0 && placed >= max) {
|
|
411
|
+
overflow = true
|
|
412
|
+
} else {
|
|
413
|
+
placed += 1
|
|
414
|
+
}
|
|
415
|
+
}
|
|
384
416
|
return {
|
|
385
417
|
key: `${widgetId || 'tab'}-${index}`,
|
|
386
418
|
widgetId,
|
|
387
419
|
label: (tab && tab.label) || widgetTitleOf(widget) || widgetId || '',
|
|
388
420
|
icon: (tab && tab.icon) || widget?.icon || '',
|
|
389
421
|
widget,
|
|
422
|
+
count,
|
|
423
|
+
overflow,
|
|
390
424
|
}
|
|
391
425
|
})
|
|
392
426
|
},
|
|
393
427
|
|
|
428
|
+
/**
|
|
429
|
+
* Name of the menu that holds the tabs that are not in the strip.
|
|
430
|
+
*
|
|
431
|
+
* @return {string|undefined} The configured label, or undefined for the default.
|
|
432
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
433
|
+
*/
|
|
434
|
+
moreLabel() {
|
|
435
|
+
const label = this.content?.moreLabel
|
|
436
|
+
return typeof label === 'string' && label !== '' ? label : undefined
|
|
437
|
+
},
|
|
438
|
+
|
|
394
439
|
/**
|
|
395
440
|
* The tab currently showing.
|
|
396
441
|
*
|
|
@@ -429,6 +474,20 @@ export default {
|
|
|
429
474
|
},
|
|
430
475
|
},
|
|
431
476
|
|
|
477
|
+
/**
|
|
478
|
+
* Open on the first tab that is in the strip. Tab 0 is the default, and
|
|
479
|
+
* when tab 0 sits under "More" the strip would open on a tab the author
|
|
480
|
+
* chose to tuck away.
|
|
481
|
+
*
|
|
482
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
483
|
+
*/
|
|
484
|
+
created() {
|
|
485
|
+
const first = this.resolvedTabs.findIndex((tab) => !tab.overflow)
|
|
486
|
+
if (first > 0) {
|
|
487
|
+
this.activeIndex = first
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
|
|
432
491
|
methods: {
|
|
433
492
|
/**
|
|
434
493
|
* Re-emit a geo child's save so the surface can reload the record.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
The current week as day columns. This example uses static items; in an app the items usually come from an OpenRegister `source`.
|
|
2
|
+
|
|
3
|
+
```vue
|
|
4
|
+
<template>
|
|
5
|
+
<CnWeekStripWidget :content="content" />
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script>
|
|
9
|
+
export default {
|
|
10
|
+
data() {
|
|
11
|
+
const day = (offset) => {
|
|
12
|
+
const now = new Date()
|
|
13
|
+
const monday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - ((now.getDay() + 6) % 7) + offset)
|
|
14
|
+
return [monday.getFullYear(), String(monday.getMonth() + 1).padStart(2, '0'), String(monday.getDate()).padStart(2, '0')].join('-')
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
content: {
|
|
18
|
+
emptyText: 'No deadlines',
|
|
19
|
+
items: [
|
|
20
|
+
{ title: 'Parking permits city centre', meta: '2026-0061 · Woo', date: day(0) },
|
|
21
|
+
{ title: 'Objection parking fine', meta: '2026-0074 · Objection', date: day(2) },
|
|
22
|
+
{ title: 'Tender youth care', meta: '2026-0058 · Woo', date: day(4) },
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Props: `content` holds the configuration (see the component description), `translate` is an optional translate function for the texts in it, and `now` sets the moment the strip treats as now.
|