@conduction/nextcloud-vue 2.93.0 → 2.94.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 +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +2 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +28 -8
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +37 -2
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +139 -3
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js +60 -12
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +91 -36
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +115 -72
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +32 -3
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +7 -7
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +141 -7
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue.js +8 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js +114 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js +45 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue2.js +38 -2
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue2.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue3.js +12 -2
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue3.js.map +1 -1
- package/dist/esm/components/CnPagination/CnPagination.vue2.js.map +1 -1
- package/dist/esm/components/CnPagination/CnPagination.vue3.js +56 -51
- package/dist/esm/components/CnPagination/CnPagination.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +3 -3
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +24 -0
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js +3 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js +39 -2
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +85 -8
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +37 -9
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -3
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +20 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +31 -20
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +9 -0
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +402 -262
- package/dist/esm/utils/dueText.js +49 -8
- package/dist/esm/utils/dueText.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +5779 -4827
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +402 -262
- package/package.json +1 -1
- package/src/components/CnActionsMenu/CnActionsMenu.vue +3 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +39 -1
- package/src/components/CnCountdownWidget/CnCountdownWidget.vue +222 -6
- package/src/components/CnDashboardPage/CnDashboardPage.vue +96 -2
- package/src/components/CnDataTable/CnDataTable.md +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +39 -10
- package/src/components/CnIndexPage/CnIndexPage.vue +144 -7
- package/src/components/CnPageHeader/CnHeaderTrail.vue +140 -0
- package/src/components/CnPageHeader/CnPageHeader.md +11 -0
- package/src/components/CnPageHeader/CnPageHeader.vue +42 -0
- package/src/components/CnPagination/CnPagination.vue +10 -2
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +3 -3
- package/src/components/CnSettingsPage/CnSettingsPage.vue +26 -0
- package/src/components/CnTabs/CnTabs.vue +39 -2
- package/src/components/CnTabsWidget/CnTabsWidget.vue +106 -13
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +4 -3
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +8 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +31 -2
- package/src/composables/indexSources.js +9 -0
- package/src/css/dialog.css +14 -0
- package/src/css/look-board-index.css +27 -1
- package/src/css/look-board.css +27 -0
- package/src/css/page-header.css +16 -0
- package/src/schemas/app-manifest-v2.schema.json +22 -15
- package/src/utils/dueText.js +49 -8
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
:title="title"
|
|
48
48
|
:description="resolvedDescription"
|
|
49
49
|
:icon="icon"
|
|
50
|
+
:breadcrumb="breadcrumb"
|
|
51
|
+
:meta="headerMeta"
|
|
50
52
|
:visuallyHidden="!showTitle">
|
|
51
53
|
<!-- Board look: the buildiq square, then the page Save as the
|
|
52
54
|
last header button. -->
|
|
@@ -475,6 +477,30 @@ export default {
|
|
|
475
477
|
default: () => t('nextcloud-vue', 'Settings'),
|
|
476
478
|
},
|
|
477
479
|
|
|
480
|
+
/**
|
|
481
|
+
* A breadcrumb under the title (manifest `config.breadcrumb`): one
|
|
482
|
+
* crumb `{ label, route?, params?, href?, icon?, separator? }` or a
|
|
483
|
+
* list; the title follows as the current crumb. Drawn as the detail
|
|
484
|
+
* header's breadcrumb row. Null (the default) draws none.
|
|
485
|
+
*
|
|
486
|
+
* @type {object|Array<object>|null}
|
|
487
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
488
|
+
*/
|
|
489
|
+
breadcrumb: {
|
|
490
|
+
type: [Object, Array],
|
|
491
|
+
default: null,
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* A note after the breadcrumb and a middle dot (manifest `config.headerMeta`).
|
|
496
|
+
*
|
|
497
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
498
|
+
*/
|
|
499
|
+
headerMeta: {
|
|
500
|
+
type: String,
|
|
501
|
+
default: '',
|
|
502
|
+
},
|
|
503
|
+
|
|
478
504
|
/** Description shown under the title when `showTitle` is set. */
|
|
479
505
|
description: {
|
|
480
506
|
type: String,
|
|
@@ -263,6 +263,21 @@ export default defineComponent({
|
|
|
263
263
|
default: '',
|
|
264
264
|
},
|
|
265
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Leave the `overflow` tabs to the host's `#nav-end` menu instead of a
|
|
268
|
+
* "More" menu of the strip's own. The strip then draws only the tabs
|
|
269
|
+
* that are not `overflow`, and an overflow tab that is selected joins
|
|
270
|
+
* it just before the last of them (board DqZaakBesluiten: Overview,
|
|
271
|
+
* Files, Contact, Tasks, Decisions, History). The host lists the
|
|
272
|
+
* overflow tabs and selects one through its `CnTab`'s `active`.
|
|
273
|
+
*
|
|
274
|
+
* @spec openspec/changes/board-tabs-overflow-in-actions/specs/board-tabs-overflow-in-actions/spec.md#requirement-board-tabs-list-their-overflow-in-the-actions-menu
|
|
275
|
+
*/
|
|
276
|
+
overflowInNavEnd: {
|
|
277
|
+
type: Boolean,
|
|
278
|
+
default: false,
|
|
279
|
+
},
|
|
280
|
+
|
|
266
281
|
/** Name of the menu that lists the `overflow` tabs. */
|
|
267
282
|
moreLabel: {
|
|
268
283
|
type: String,
|
|
@@ -377,14 +392,36 @@ export default defineComponent({
|
|
|
377
392
|
*
|
|
378
393
|
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
379
394
|
*/
|
|
380
|
-
const visibleTabs = computed(() =>
|
|
395
|
+
const visibleTabs = computed(() => {
|
|
396
|
+
if (props.overflowInNavEnd) {
|
|
397
|
+
return navEndStrip()
|
|
398
|
+
}
|
|
399
|
+
return tabs.filter((tab) => boardStrip.value || !tab.overflow || tab.uid === activeUid.value)
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* The strip under `overflowInNavEnd`: the tabs that are not
|
|
404
|
+
* `overflow`, with a selected overflow tab placed before the last.
|
|
405
|
+
*
|
|
406
|
+
* @return {object[]} The tabs to draw, in order.
|
|
407
|
+
* @spec openspec/changes/board-tabs-overflow-in-actions/specs/board-tabs-overflow-in-actions/spec.md#requirement-an-opened-overflow-tab-joins-the-strip-before-the-last-tab
|
|
408
|
+
*/
|
|
409
|
+
function navEndStrip() {
|
|
410
|
+
const pinned = tabs.filter((tab) => !tab.overflow)
|
|
411
|
+
const opened = tabs.find((tab) => tab.overflow && tab.uid === activeUid.value)
|
|
412
|
+
if (!opened) {
|
|
413
|
+
return pinned
|
|
414
|
+
}
|
|
415
|
+
const at = Math.max(0, pinned.length - 1)
|
|
416
|
+
return [...pinned.slice(0, at), opened, ...pinned.slice(at)]
|
|
417
|
+
}
|
|
381
418
|
|
|
382
419
|
/**
|
|
383
420
|
* The tabs listed under "More": overflow tabs that are not selected.
|
|
384
421
|
*
|
|
385
422
|
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
386
423
|
*/
|
|
387
|
-
const overflowTabs = computed(() => (boardStrip.value ? [] : tabs.filter((tab) => tab.overflow && tab.uid !== activeUid.value)))
|
|
424
|
+
const overflowTabs = computed(() => (boardStrip.value || props.overflowInNavEnd ? [] : tabs.filter((tab) => tab.overflow && tab.uid !== activeUid.value)))
|
|
388
425
|
|
|
389
426
|
/**
|
|
390
427
|
* Whether a tab declares a count. `0` is a count; null and '' are not.
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
:aria-label="stripLabel"
|
|
10
10
|
:moreLabel="moreLabel"
|
|
11
11
|
:variant="stripVariant"
|
|
12
|
+
:overflowInNavEnd="isBoard"
|
|
12
13
|
class="cn-tabs-widget__tabs"
|
|
13
14
|
@update:activeIndex="onTabChange">
|
|
14
15
|
<!-- One Actions menu for the whole widget, bound to whichever child
|
|
@@ -27,10 +28,11 @@
|
|
|
27
28
|
{{ cnTranslate(activeHeaderLink.label) }}
|
|
28
29
|
</component>
|
|
29
30
|
<CnActionsMenu
|
|
30
|
-
v-if="showActions"
|
|
31
|
-
:showRefresh="showRefresh"
|
|
32
|
-
:showRequestFeature="showRequestFeature"
|
|
33
|
-
:showDocumentation="showDocumentation"
|
|
31
|
+
v-if="showActions || menuTabs.length"
|
|
32
|
+
:showRefresh="showActions && showRefresh"
|
|
33
|
+
:showRequestFeature="showActions && showRequestFeature"
|
|
34
|
+
:showDocumentation="showActions && showDocumentation"
|
|
35
|
+
:showReportBug="showActions"
|
|
34
36
|
:documentationUrl="documentationUrl"
|
|
35
37
|
:widgetId="activeWidgetId"
|
|
36
38
|
:title="activeTitle"
|
|
@@ -38,6 +40,20 @@
|
|
|
38
40
|
:surface="`widget:${activeWidgetId}`"
|
|
39
41
|
refreshChannel="cn:widget:refresh"
|
|
40
42
|
testidBase="cn-tabs-widget">
|
|
43
|
+
<!-- Board look: the tabs that are not in the strip, at the
|
|
44
|
+
top of the menu, in place of a "More" button. -->
|
|
45
|
+
<template v-if="menuTabs.length" #lead-items>
|
|
46
|
+
<NcActionButton
|
|
47
|
+
v-for="entry in menuTabs"
|
|
48
|
+
:key="entry.key"
|
|
49
|
+
:disabled="entry.pending || null"
|
|
50
|
+
:closeAfterClick="true"
|
|
51
|
+
data-testid="cn-tabs-widget-menu-tab"
|
|
52
|
+
@click="openMenuTab(entry)">
|
|
53
|
+
{{ menuTabLabel(entry) }}
|
|
54
|
+
</NcActionButton>
|
|
55
|
+
<NcActionSeparator v-if="showActions" />
|
|
56
|
+
</template>
|
|
41
57
|
<!-- The open panel's own items, below the built-in trio.
|
|
42
58
|
A panel draws no header, so these would otherwise have
|
|
43
59
|
nowhere to go: the data widget's Metadata and full edit
|
|
@@ -51,7 +67,7 @@
|
|
|
51
67
|
tab mounted, so several panels publish at once and an
|
|
52
68
|
unfiltered list would offer actions for a sheet nobody
|
|
53
69
|
is looking at. -->
|
|
54
|
-
<template v-if="activePanelActions.length" #action-items>
|
|
70
|
+
<template v-if="showActions && activePanelActions.length" #action-items>
|
|
55
71
|
<NcActionButton
|
|
56
72
|
v-for="action in activePanelActions"
|
|
57
73
|
:key="action.key"
|
|
@@ -175,7 +191,7 @@
|
|
|
175
191
|
<script>
|
|
176
192
|
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
|
|
177
193
|
import { translate as t } from '@nextcloud/l10n'
|
|
178
|
-
import { NcActionButton, NcLoadingIcon } from '@nextcloud/vue'
|
|
194
|
+
import { NcActionButton, NcActionSeparator, NcLoadingIcon } from '@nextcloud/vue'
|
|
179
195
|
import CnDetailWidgetHost from '../CnDetailWidgetHost/CnDetailWidgetHost.vue'
|
|
180
196
|
import CnEmptyContent from '../CnEmptyContent/CnEmptyContent.vue'
|
|
181
197
|
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
@@ -184,6 +200,7 @@ import CnTabs from '../CnTabs/CnTabs.vue'
|
|
|
184
200
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
185
201
|
import { headerLinkTarget, widgetHeaderLink } from '../../utils/contentHeaderLink.js'
|
|
186
202
|
import { resolveTabCount } from '../../utils/detailActionModel.js'
|
|
203
|
+
import { detailWidgetVisible } from '../../utils/detailWidgetVisibility.js'
|
|
187
204
|
import { fetchListTotal } from '../../utils/fetchFilterCounts.js'
|
|
188
205
|
import { isActivityWidget } from '../../utils/headerMeta.js'
|
|
189
206
|
import { PANEL_ACTION_SINK } from '../../utils/panelActions.js'
|
|
@@ -251,6 +268,7 @@ export default {
|
|
|
251
268
|
CnTab,
|
|
252
269
|
CnTabs,
|
|
253
270
|
NcActionButton,
|
|
271
|
+
NcActionSeparator,
|
|
254
272
|
CnEmptyContent,
|
|
255
273
|
NcLoadingIcon,
|
|
256
274
|
},
|
|
@@ -364,7 +382,8 @@ export default {
|
|
|
364
382
|
* `layout: [{ widgetId, gridWidth }]` sets a width (of 12) per widget, as
|
|
365
383
|
* the page grid does. Widgets wrap onto the next row.
|
|
366
384
|
*
|
|
367
|
-
* `maxVisibleTabs` caps the strip: later tabs go under "More"
|
|
385
|
+
* `maxVisibleTabs` caps the strip: later tabs go under "More" (in the
|
|
386
|
+
* board look: into the Actions menu, with History kept in the strip).
|
|
368
387
|
* `hideEmpty: true` moves a tab whose count is 0 there too.
|
|
369
388
|
* `moreLabel` names that menu. `variant: "segmented"` draws the strip
|
|
370
389
|
* as a pill switch (CnTabs' segmented variant); `line` is the default.
|
|
@@ -552,16 +571,34 @@ export default {
|
|
|
552
571
|
return [...(forId.host || []), ...(forId.widget || [])]
|
|
553
572
|
},
|
|
554
573
|
|
|
574
|
+
/**
|
|
575
|
+
* The tabs the board look lists at the top of the Actions menu: the
|
|
576
|
+
* overflow tabs, less the one that is open (it is in the strip).
|
|
577
|
+
* Empty outside the board look, where CnTabs draws "More".
|
|
578
|
+
*
|
|
579
|
+
* @return {object[]} Resolved tabs, in strip order.
|
|
580
|
+
* @spec openspec/changes/board-tabs-overflow-in-actions/specs/board-tabs-overflow-in-actions/spec.md#requirement-board-tabs-list-their-overflow-in-the-actions-menu
|
|
581
|
+
*/
|
|
582
|
+
menuTabs() {
|
|
583
|
+
if (!this.isBoard) {
|
|
584
|
+
return []
|
|
585
|
+
}
|
|
586
|
+
return this.resolvedTabs.filter((entry, index) => entry.overflow && index !== this.activeIndex)
|
|
587
|
+
},
|
|
588
|
+
|
|
555
589
|
/**
|
|
556
590
|
* The configured tabs, each paired with the widget definition it names.
|
|
557
591
|
*
|
|
558
592
|
* A tab goes under the strip's "More" menu when it says `overflow`, when
|
|
559
593
|
* `content.hideEmpty` is set and its count is 0, or when
|
|
560
594
|
* `content.maxVisibleTabs` places are already taken. `count` is a
|
|
561
|
-
* literal, or read off the record through `countField`.
|
|
595
|
+
* literal, or read off the record through `countField`. In the board
|
|
596
|
+
* look those tabs are listed in the Actions menu instead, and the
|
|
597
|
+
* activity (History) tab always stays in the strip.
|
|
562
598
|
*
|
|
563
599
|
* @return {object[]} `{ key, widgetId, label, icon, widget, count, overflow }` per tab.
|
|
564
600
|
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
601
|
+
* @spec openspec/changes/board-tabs-overflow-in-actions/specs/board-tabs-overflow-in-actions/spec.md#requirement-board-tabs-list-their-overflow-in-the-actions-menu
|
|
565
602
|
*/
|
|
566
603
|
resolvedTabs() {
|
|
567
604
|
const authored = Array.isArray(this.content?.tabs) ? this.content.tabs : []
|
|
@@ -580,9 +617,12 @@ export default {
|
|
|
580
617
|
const originalIndex = ordered.map(({ index }) => index)
|
|
581
618
|
const max = Number(this.content?.maxVisibleTabs)
|
|
582
619
|
const hideEmpty = this.content?.hideEmpty === true
|
|
620
|
+
// The board keeps its History tab in the strip, so it holds one of
|
|
621
|
+
// the `maxVisibleTabs` places from the start.
|
|
622
|
+
const pinActivity = this.isBoard && tabs.some((tab, position) => isActivityTab(tab) && this.tabVisibility(tab, originalIndex[position]) !== 'hidden')
|
|
583
623
|
// How many tabs have taken a place in the strip so far. A tab that
|
|
584
624
|
// is already going under "More" does not use one up.
|
|
585
|
-
let placed = 0
|
|
625
|
+
let placed = pinActivity ? 1 : 0
|
|
586
626
|
const resolved = []
|
|
587
627
|
tabs.forEach((tab, position) => {
|
|
588
628
|
const index = originalIndex[position]
|
|
@@ -596,7 +636,7 @@ export default {
|
|
|
596
636
|
const ids = this.tabWidgetIds(tab)
|
|
597
637
|
const widgets = ids.length > 1
|
|
598
638
|
? ids.map((id) => this.availableWidgets.find((w) => w && w.id === id) || null)
|
|
599
|
-
.filter((w) => w && !this.hiddenByWorkspace(w))
|
|
639
|
+
.filter((w) => w && !this.hiddenByWorkspace(w) && !this.hiddenByRecord(w))
|
|
600
640
|
: []
|
|
601
641
|
const widget = ids.length > 1
|
|
602
642
|
? (widgets[0] || null)
|
|
@@ -605,9 +645,9 @@ export default {
|
|
|
605
645
|
const declared = resolveTabCount(typeof tab === 'object' ? tab : null, this.objectData)
|
|
606
646
|
const counted = this.widgetCounts[index]
|
|
607
647
|
const count = declared === null && typeof counted === 'number' ? counted : declared
|
|
608
|
-
|
|
609
|
-
let overflow = !
|
|
610
|
-
if (!overflow && !
|
|
648
|
+
const pinned = pinActivity && isActivityTab(tab)
|
|
649
|
+
let overflow = !pinned && ((tab && tab.overflow === true) || (hideEmpty && count === 0))
|
|
650
|
+
if (!overflow && !pinned) {
|
|
611
651
|
if (Number.isFinite(max) && max > 0 && placed >= max) {
|
|
612
652
|
overflow = true
|
|
613
653
|
} else {
|
|
@@ -868,6 +908,26 @@ export default {
|
|
|
868
908
|
return !evaluateWorkspaceCondition(cond, 'value' in bag ? bag.value : bag)
|
|
869
909
|
},
|
|
870
910
|
|
|
911
|
+
/**
|
|
912
|
+
* Whether the record hides a widget of a stack on a detail page: its
|
|
913
|
+
* own `visibleWhen` reads a field of the record or of a record the
|
|
914
|
+
* page's `visibilityContext` names (`@context.<name>`), and does not
|
|
915
|
+
* hold, as a grid widget's would not. Hidden while those records
|
|
916
|
+
* load. Off a detail page (no record), and for a condition that needs
|
|
917
|
+
* a request, nothing is hidden here.
|
|
918
|
+
*
|
|
919
|
+
* @param {object} widget The widget definition.
|
|
920
|
+
* @return {boolean} True when the widget must not draw.
|
|
921
|
+
* @spec openspec/changes/tab-stack-child-visible-when/specs/tab-stack-child-visible-when/spec.md#requirement-a-widget-in-a-tab-stack-on-a-detail-page-follows-its-own-condition
|
|
922
|
+
*/
|
|
923
|
+
hiddenByRecord(widget) {
|
|
924
|
+
const cond = widget.visibleWhen
|
|
925
|
+
if (!this.objectData || !cond || typeof cond !== 'object' || isWorkspaceCondition(cond) || !isLocallyDecidableVisibleWhen(cond)) {
|
|
926
|
+
return false
|
|
927
|
+
}
|
|
928
|
+
return !detailWidgetVisible(widget, this.conditionData)
|
|
929
|
+
},
|
|
930
|
+
|
|
871
931
|
/**
|
|
872
932
|
* The grid a stack lays its widgets out in, as one column span (of 12)
|
|
873
933
|
* per widget in order, or null for the plain vertical stack. A tab says
|
|
@@ -1139,6 +1199,39 @@ export default {
|
|
|
1139
1199
|
}
|
|
1140
1200
|
},
|
|
1141
1201
|
|
|
1202
|
+
/**
|
|
1203
|
+
* Open a tab picked from the Actions menu, then move focus to it in
|
|
1204
|
+
* the strip, so a keyboard user lands on the tab they chose.
|
|
1205
|
+
*
|
|
1206
|
+
* @param {object} entry The resolved tab.
|
|
1207
|
+
* @return {void}
|
|
1208
|
+
* @spec openspec/changes/board-tabs-overflow-in-actions/specs/board-tabs-overflow-in-actions/spec.md#requirement-board-tabs-list-their-overflow-in-the-actions-menu
|
|
1209
|
+
*/
|
|
1210
|
+
openMenuTab(entry) {
|
|
1211
|
+
const index = this.resolvedTabs.indexOf(entry)
|
|
1212
|
+
if (index < 0) {
|
|
1213
|
+
return
|
|
1214
|
+
}
|
|
1215
|
+
this.activeIndex = index
|
|
1216
|
+
this.$nextTick(() => {
|
|
1217
|
+
const button = this.$el?.querySelector?.('[role="tab"][aria-selected="true"]')
|
|
1218
|
+
if (button && typeof button.focus === 'function') {
|
|
1219
|
+
button.focus()
|
|
1220
|
+
}
|
|
1221
|
+
})
|
|
1222
|
+
},
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* A menu tab's text: its label, and its count when it has one.
|
|
1226
|
+
*
|
|
1227
|
+
* @param {object} entry The resolved tab.
|
|
1228
|
+
* @return {string} The menu label.
|
|
1229
|
+
*/
|
|
1230
|
+
menuTabLabel(entry) {
|
|
1231
|
+
const hasCount = entry.count !== null && entry.count !== undefined && entry.count !== ''
|
|
1232
|
+
return hasCount ? `${entry.label} (${entry.count})` : entry.label
|
|
1233
|
+
},
|
|
1234
|
+
|
|
1142
1235
|
/**
|
|
1143
1236
|
* Empty-state text for a tab whose widget id resolves to nothing.
|
|
1144
1237
|
*
|
|
@@ -45,10 +45,11 @@
|
|
|
45
45
|
<span class="cn-version-info__value">{{ appVersion }}</span>
|
|
46
46
|
</div>
|
|
47
47
|
|
|
48
|
-
<!-- Configured
|
|
49
|
-
|
|
48
|
+
<!-- Configured version: when provided, and always in the board look,
|
|
49
|
+
which draws the row ("Unknown" until the app reports it). -->
|
|
50
|
+
<div v-if="configuredVersion || isBoard" class="cn-version-info__item" data-testid="cn-version-info-configured">
|
|
50
51
|
<span class="cn-version-info__label">{{ fieldLabels.configuredVersion }}:</span>
|
|
51
|
-
<span class="cn-version-info__value">{{ configuredVersion }}</span>
|
|
52
|
+
<span class="cn-version-info__value">{{ configuredVersion || t('nextcloud-vue', 'Unknown') }}</span>
|
|
52
53
|
</div>
|
|
53
54
|
|
|
54
55
|
<!-- Additional items -->
|
|
@@ -177,3 +177,11 @@ Request a feature / Report a bug / Documentation render on every widget; the sha
|
|
|
177
177
|
### A text link in the header (`headerLink`)
|
|
178
178
|
|
|
179
179
|
`headerLink` (`{ label, route?, params?, query?, href? }`) draws one text link in the header before the overflow menu, such as "All deadlines".
|
|
180
|
+
|
|
181
|
+
Subtitle: `subtitle` is a muted line under the title, the counterpart of `eyebrow` above it:
|
|
182
|
+
|
|
183
|
+
```vue
|
|
184
|
+
<CnWidgetWrapper title="Op tijd beslist per zaaktype" subtitle="Dit kwartaal, beslist binnen de termijn">
|
|
185
|
+
<p>…</p>
|
|
186
|
+
</CnWidgetWrapper>
|
|
187
|
+
```
|
|
@@ -54,11 +54,14 @@
|
|
|
54
54
|
class="cn-widget-wrapper__icon" />
|
|
55
55
|
<!-- An eyebrow (`eyebrow`) is a muted line above the title:
|
|
56
56
|
"Saldo per week" above "Prognose 13 weken". -->
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
<!-- A subtitle (`subtitle`) is a muted line under the title:
|
|
58
|
+
"Dit kwartaal, beslist binnen de termijn". -->
|
|
59
|
+
<div v-if="resolvedEyebrow || resolvedSubtitle" class="cn-widget-wrapper__title-stack">
|
|
60
|
+
<span v-if="resolvedEyebrow" class="cn-widget-wrapper__eyebrow" data-testid="cn-widget-wrapper-eyebrow">{{ resolvedEyebrow }}</span>
|
|
59
61
|
<h3 :id="titleId" class="cn-widget-wrapper__title">
|
|
60
62
|
{{ resolvedTitle }}
|
|
61
63
|
</h3>
|
|
64
|
+
<span v-if="resolvedSubtitle" class="cn-widget-wrapper__subtitle" data-testid="cn-widget-wrapper-subtitle">{{ resolvedSubtitle }}</span>
|
|
62
65
|
</div>
|
|
63
66
|
<h3 v-else :id="titleId" class="cn-widget-wrapper__title">
|
|
64
67
|
{{ resolvedTitle }}
|
|
@@ -264,6 +267,17 @@ export default {
|
|
|
264
267
|
default: '',
|
|
265
268
|
},
|
|
266
269
|
|
|
270
|
+
/**
|
|
271
|
+
* A muted line under the title, such as the period a chart covers.
|
|
272
|
+
* Run through the host translate function. Empty draws none.
|
|
273
|
+
*
|
|
274
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-dashboard-widget-card-can-carry-a-line-under-its-title
|
|
275
|
+
*/
|
|
276
|
+
subtitle: {
|
|
277
|
+
type: String,
|
|
278
|
+
default: '',
|
|
279
|
+
},
|
|
280
|
+
|
|
267
281
|
/** Whether to show the header with title */
|
|
268
282
|
showTitle: {
|
|
269
283
|
type: Boolean,
|
|
@@ -733,6 +747,15 @@ export default {
|
|
|
733
747
|
return this.eyebrow ? this.effectiveTranslate(this.eyebrow) : ''
|
|
734
748
|
},
|
|
735
749
|
|
|
750
|
+
/**
|
|
751
|
+
* The subtitle run through the host translate function.
|
|
752
|
+
*
|
|
753
|
+
* @return {string}
|
|
754
|
+
*/
|
|
755
|
+
resolvedSubtitle() {
|
|
756
|
+
return this.subtitle ? this.effectiveTranslate(this.subtitle) : ''
|
|
757
|
+
},
|
|
758
|
+
|
|
736
759
|
/**
|
|
737
760
|
* The RENDERED widget title — `displayTitle` run through the host
|
|
738
761
|
* translate function. Kept separate from `displayTitle` on purpose:
|
|
@@ -1175,6 +1198,12 @@ export default {
|
|
|
1175
1198
|
min-width: 0;
|
|
1176
1199
|
}
|
|
1177
1200
|
|
|
1201
|
+
.cn-widget-wrapper__subtitle {
|
|
1202
|
+
font-size: 13px;
|
|
1203
|
+
line-height: 1.3;
|
|
1204
|
+
color: var(--color-text-maxcontrast);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1178
1207
|
.cn-widget-wrapper__eyebrow {
|
|
1179
1208
|
font-size: 13px;
|
|
1180
1209
|
line-height: 1.3;
|
|
@@ -267,6 +267,15 @@ function tasksSource() {
|
|
|
267
267
|
*/
|
|
268
268
|
loading: () => !!store.loading,
|
|
269
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Count the inbox for a config without touching the rows on screen,
|
|
272
|
+
* for a quick-filter tab with `showCount`.
|
|
273
|
+
*
|
|
274
|
+
* @param {object} config The merged config, as for `load`.
|
|
275
|
+
* @return {Promise<number>} The datastore total.
|
|
276
|
+
*/
|
|
277
|
+
count: async (config) => (await store.fetchFor({ ...(config || {}), limit: 1 })).total,
|
|
278
|
+
|
|
270
279
|
columns: [
|
|
271
280
|
{ key: 'title', label: t('nextcloud-vue', 'Task') },
|
|
272
281
|
{ key: 'subjectLabel', label: t('nextcloud-vue', 'Subject') },
|
package/src/css/dialog.css
CHANGED
|
@@ -250,3 +250,17 @@
|
|
|
250
250
|
min-height: 44px;
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
|
+
|
|
254
|
+
/* Above a phone the boards top-align a dialog (DqNieuweZaak, DqUwWachtrij,
|
|
255
|
+
DqVolgendeStap: the top edge sits about 90px down at 1440 wide) instead of
|
|
256
|
+
centring it. `--cn-dialog-top` moves it. */
|
|
257
|
+
@media (min-width: 601px) {
|
|
258
|
+
.modal-mask.cn-dialog-board .modal-wrapper {
|
|
259
|
+
align-items: flex-start;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.modal-mask.cn-dialog-board .modal-wrapper .modal-container {
|
|
263
|
+
margin-top: var(--cn-dialog-top, 88px);
|
|
264
|
+
max-height: calc(100% - var(--cn-dialog-top, 88px) - 24px);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -481,10 +481,11 @@
|
|
|
481
481
|
border-radius: 0;
|
|
482
482
|
}
|
|
483
483
|
|
|
484
|
+
/* The header row is near-white on the boards (#fafbfc), not the hover grey. */
|
|
484
485
|
.cn-look-board .cn-table-container--board .cn-data-table th {
|
|
485
486
|
padding: 12px 16px;
|
|
486
487
|
border-bottom: 0;
|
|
487
|
-
background: var(--color-background-hover);
|
|
488
|
+
background: var(--cn-board-table-head-bg, color-mix(in srgb, var(--color-background-hover) 40%, var(--color-main-background)));
|
|
488
489
|
color: var(--color-text-maxcontrast);
|
|
489
490
|
font-weight: 600;
|
|
490
491
|
}
|
|
@@ -542,6 +543,15 @@
|
|
|
542
543
|
max-width: calc(100% - 26px);
|
|
543
544
|
}
|
|
544
545
|
|
|
546
|
+
/* The board's funnel: a 22px button 4px after the sort chevron. */
|
|
547
|
+
.cn-look-board .cn-table-container--board .cn-data-table th .cn-table-header__filter {
|
|
548
|
+
width: 22px;
|
|
549
|
+
min-width: 22px;
|
|
550
|
+
height: 22px;
|
|
551
|
+
min-height: 22px;
|
|
552
|
+
margin-inline-start: 4px;
|
|
553
|
+
}
|
|
554
|
+
|
|
545
555
|
/* The selection column: 18px checkboxes, 28px of content after 16px of padding.
|
|
546
556
|
The box model is set with `!important` and the widths include the padding:
|
|
547
557
|
Nextcloud's server.css sets `box-sizing: border-box` on everything under
|
|
@@ -747,6 +757,14 @@
|
|
|
747
757
|
padding: 0 12px;
|
|
748
758
|
}
|
|
749
759
|
|
|
760
|
+
/* The inert Next of a single page: a faint outline, muted text. */
|
|
761
|
+
.cn-look-board .cn-pagination--board .cn-pagination__board-link--disabled {
|
|
762
|
+
display: inline-flex;
|
|
763
|
+
align-items: center;
|
|
764
|
+
color: var(--color-text-maxcontrast);
|
|
765
|
+
cursor: default;
|
|
766
|
+
}
|
|
767
|
+
|
|
750
768
|
.cn-look-board .cn-pagination--board .cn-pagination__board-link--current {
|
|
751
769
|
border-color: var(--color-primary-element);
|
|
752
770
|
background: var(--color-primary-element);
|
|
@@ -1028,3 +1046,11 @@
|
|
|
1028
1046
|
padding: 0 12px;
|
|
1029
1047
|
border-radius: 8px;
|
|
1030
1048
|
}
|
|
1049
|
+
|
|
1050
|
+
/* The sidebar's own open toggle (NcAppSidebar's `.app-sidebar__toggle`, drawn
|
|
1051
|
+
when the sidebar is closed) sits over the content's top-right corner, which
|
|
1052
|
+
on an index page is the primary header button: a stray dark square on
|
|
1053
|
+
"New contact". The board draws none; the Filter button opens the sidebar. */
|
|
1054
|
+
.cn-look-board:has(.cn-index-page) > .app-sidebar__toggle {
|
|
1055
|
+
display: none;
|
|
1056
|
+
}
|
package/src/css/look-board.css
CHANGED
|
@@ -624,6 +624,33 @@
|
|
|
624
624
|
opacity: 1 !important;
|
|
625
625
|
}
|
|
626
626
|
|
|
627
|
+
/* The support card at the end of an admin page (`supportPosition: "end"`,
|
|
628
|
+
board PqBeheer): one row, the title then the lines, wrapping in a narrow
|
|
629
|
+
window, with less padding than a settings card.
|
|
630
|
+
@spec openspec/changes/admin-shell-support-end/specs/settings-board-look/spec.md#requirement-support-can-be-its-own-card-at-the-end */
|
|
631
|
+
.cn-look-board .settings-section.cn-admin-settings-shell__support:not(.cn-look-nextcloud *) {
|
|
632
|
+
flex-direction: row !important;
|
|
633
|
+
flex-wrap: wrap;
|
|
634
|
+
align-items: center;
|
|
635
|
+
gap: 10px 24px;
|
|
636
|
+
padding: 16px 24px;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.cn-look-board .cn-admin-settings-shell__support:not(.cn-look-nextcloud *) .settings-section__name {
|
|
640
|
+
font-size: 16px;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.cn-look-board .cn-admin-settings-shell__support:not(.cn-look-nextcloud *) .cn-settings-section__content {
|
|
644
|
+
display: flex;
|
|
645
|
+
flex-wrap: wrap;
|
|
646
|
+
gap: 10px 24px;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.cn-look-board .cn-admin-settings-shell__support:not(.cn-look-nextcloud *) .cn-admin-settings-shell__support-line {
|
|
650
|
+
margin: 0;
|
|
651
|
+
font-size: 14px;
|
|
652
|
+
}
|
|
653
|
+
|
|
627
654
|
/* The section footer is the save row: the hairline replaces its own rule. */
|
|
628
655
|
.cn-look-board .settings-section .cn-settings-section__footer {
|
|
629
656
|
margin: 0;
|
package/src/css/page-header.css
CHANGED
|
@@ -61,3 +61,19 @@
|
|
|
61
61
|
white-space: nowrap;
|
|
62
62
|
border: 0;
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
/* The breadcrumb and meta line under the title (CnHeaderTrail). In the board
|
|
66
|
+
look it takes the detail header row's styles; here it only lays the crumbs,
|
|
67
|
+
the dot and the note out on one line. */
|
|
68
|
+
.cn-header-trail {
|
|
69
|
+
align-items: center;
|
|
70
|
+
color: var(--color-text-maxcontrast);
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-wrap: wrap;
|
|
73
|
+
gap: var(--default-grid-baseline);
|
|
74
|
+
margin-bottom: var(--default-grid-baseline);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.cn-header-trail .cn-detail-page__breadcrumbs {
|
|
78
|
+
margin-bottom: 0;
|
|
79
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"$id": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json",
|
|
4
4
|
"title": "Conduction App Manifest v2",
|
|
5
5
|
"description": "v2 schema for the JSON-driven page and navigation manifest consumed by @conduction/nextcloud-vue. Introduces a uniform widgets[] array on every page type with a per-slot grid coordinate system, a typed actions[] discriminator, and the required $schema field for version detection. v1 manifests continue to validate against app-manifest.schema.json (unchanged).",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.115.0",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"required": ["$schema", "version"],
|
|
9
9
|
"allOf": [
|
|
@@ -533,6 +533,21 @@
|
|
|
533
533
|
}
|
|
534
534
|
},
|
|
535
535
|
"$defs": {
|
|
536
|
+
"breadcrumbCrumb": {
|
|
537
|
+
"type": "object",
|
|
538
|
+
"additionalProperties": false,
|
|
539
|
+
"required": ["label"],
|
|
540
|
+
"description": "One breadcrumb crumb (config.breadcrumb).",
|
|
541
|
+
"properties": {
|
|
542
|
+
"label": { "type": "string", "minLength": 1 },
|
|
543
|
+
"route": { "type": "string" },
|
|
544
|
+
"params": { "type": "object", "additionalProperties": true },
|
|
545
|
+
"href": { "type": "string" },
|
|
546
|
+
"icon": { "type": "string", "minLength": 1, "description": "Optional MDI icon name (e.g. `Home`) drawn as the first crumb instead of the label, which then becomes its accessible name (zuiddrecht-pixel-gaps-2). Without it the label shows as text." },
|
|
547
|
+
"currentField": { "type": "string", "minLength": 1, "description": "A field of the record (dotted path, e.g. `identifier`) whose value is the current crumb instead of the display name; an empty value falls back to the display name (zuiddrecht-pixel-gaps-3)." },
|
|
548
|
+
"separator": { "type": "string", "minLength": 1, "maxLength": 3, "description": "Text drawn between the crumbs (e.g. `/`) instead of the chevron (zuiddrecht-pixel-gaps-3)." }
|
|
549
|
+
}
|
|
550
|
+
},
|
|
536
551
|
"rowIndicator": {
|
|
537
552
|
"type": "object",
|
|
538
553
|
"additionalProperties": false,
|
|
@@ -4259,7 +4274,7 @@
|
|
|
4259
4274
|
"headerMeta": {
|
|
4260
4275
|
"type": "string",
|
|
4261
4276
|
"minLength": 1,
|
|
4262
|
-
"description": "type='detail', board look: the meta line on row 2 of the header, after the breadcrumb and a middle dot (screens-detail-page-parity). A field template such as `\"via {channel}\"`: every `{path}` is read from the record, and a line with an empty value is dropped. Maps to CnDetailPage `headerMeta`."
|
|
4277
|
+
"description": "type='detail', board look: the meta line on row 2 of the header, after the breadcrumb and a middle dot (screens-detail-page-parity). A field template such as `\"via {channel}\"`: every `{path}` is read from the record, and a line with an empty value is dropped. Maps to CnDetailPage `headerMeta`. type='index' | 'dashboard' | 'settings' (page-header-breadcrumb): the note after the breadcrumb and a middle dot under the title; on an index page `{total}` and `{shown}` are the list's counts."
|
|
4263
4278
|
},
|
|
4264
4279
|
"tabsLabel": {
|
|
4265
4280
|
"type": "string",
|
|
@@ -4499,19 +4514,11 @@
|
|
|
4499
4514
|
"description": "type='detail': `false` drops the type eyebrow (the type label above the record name) once the record resolves, for a header that says the type in a pill instead (zuiddrecht-pixel-gaps). Maps to CnDetailPage `showTypeEyebrow`."
|
|
4500
4515
|
},
|
|
4501
4516
|
"breadcrumb": {
|
|
4502
|
-
"
|
|
4503
|
-
"
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
"label": { "type": "string", "minLength": 1 },
|
|
4508
|
-
"route": { "type": "string" },
|
|
4509
|
-
"params": { "type": "object", "additionalProperties": true },
|
|
4510
|
-
"href": { "type": "string" },
|
|
4511
|
-
"icon": { "type": "string", "minLength": 1, "description": "Optional MDI icon name (e.g. `Home`) drawn as the first crumb instead of the label, which then becomes its accessible name (zuiddrecht-pixel-gaps-2). Without it the label shows as text." },
|
|
4512
|
-
"currentField": { "type": "string", "minLength": 1, "description": "A field of the record (dotted path, e.g. `identifier`) whose value is the current crumb instead of the display name; an empty value falls back to the display name (zuiddrecht-pixel-gaps-3)." },
|
|
4513
|
-
"separator": { "type": "string", "minLength": 1, "maxLength": 3, "description": "Text drawn between the crumbs (e.g. `/`) instead of the chevron (zuiddrecht-pixel-gaps-3)." }
|
|
4514
|
-
}
|
|
4517
|
+
"description": "A breadcrumb line. type='detail' (zuiddrecht-pixel-gaps): one crumb naming the list the record belongs to (`route` + optional `params`, or `href`); the record's display name follows as the current crumb. Maps to CnDetailPage `breadcrumb`. type='index' | 'dashboard' | 'settings' (page-header-breadcrumb): one crumb or a list, drawn under the title in the same row style; the page title follows as the current crumb. Maps to the page's `breadcrumb`.",
|
|
4518
|
+
"anyOf": [
|
|
4519
|
+
{ "$ref": "#/$defs/breadcrumbCrumb" },
|
|
4520
|
+
{ "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/breadcrumbCrumb" } }
|
|
4521
|
+
]
|
|
4515
4522
|
},
|
|
4516
4523
|
"headerCard": {
|
|
4517
4524
|
"type": "boolean",
|