@conduction/nextcloud-vue 2.66.0 → 2.68.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/CnActionsBar/CnActionsBar.vue2.js +19 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +10 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +2 -2
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +37 -26
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js +3 -3
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +4 -4
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +87 -18
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +6 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +89 -49
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +337 -315
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/defaultActions.js +6 -7
- package/dist/esm/components/CnIndexPage/defaultActions.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +33 -34
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue3.js +3 -3
- package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +13 -13
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +3 -2
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useContextMenu.js +4 -2
- package/dist/esm/composables/useContextMenu.js.map +1 -1
- package/dist/esm/composables/useDependencyCheck.js +44 -1
- package/dist/esm/composables/useDependencyCheck.js.map +1 -1
- package/dist/esm/composables/useListView.js +43 -1
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/composables/useSetupStatus.js +26 -3
- package/dist/esm/composables/useSetupStatus.js.map +1 -1
- package/dist/esm/l10n/en.json.js +4 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +4 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +20 -2
- package/dist/esm/store/plugins/liveUpdates.js +19 -9
- package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
- package/dist/esm/store/useObjectStore.js +20 -9
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/resolveRowActions.js +130 -0
- package/dist/esm/utils/resolveRowActions.js.map +1 -0
- package/dist/esm/utils/rowActionItem.js +104 -0
- package/dist/esm/utils/rowActionItem.js.map +1 -0
- package/dist/esm/utils/timelineEvents.js +44 -1
- package/dist/esm/utils/timelineEvents.js.map +1 -1
- package/dist/esm/utils/validateManifest.js +59 -3
- package/dist/esm/utils/validateManifest.js.map +1 -1
- 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 +1431 -802
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +20 -2
- package/l10n/en.json +4 -1
- package/l10n/nl.json +4 -1
- package/package.json +1 -1
- package/scripts/.jsdoc-baselines.json +2 -2
- package/src/components/CnActionsBar/CnActionsBar.md +2 -1
- package/src/components/CnActionsBar/CnActionsBar.vue +25 -1
- package/src/components/CnAppNav/CnAppNav.vue +2 -2
- package/src/components/CnContextMenu/CnContextMenu.vue +40 -29
- package/src/components/CnDataTable/CnDataTable.md +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +4 -4
- package/src/components/CnFormDialog/CnFormDialog.vue +92 -18
- package/src/components/CnIndexPage/CnIndexPage.md +23 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +108 -52
- package/src/components/CnIndexPage/defaultActions.js +6 -7
- package/src/components/CnRowActions/CnRowActions.vue +36 -37
- package/src/components/CnSetupWizard/CnSetupWizard.vue +13 -13
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +3 -2
- package/src/composables/useContextMenu.js +4 -2
- package/src/composables/useDependencyCheck.js +43 -0
- package/src/composables/useListView.js +43 -1
- package/src/composables/useSetupStatus.js +26 -3
- package/src/css/actions-bar.css +20 -2
- package/src/schemas/app-manifest-v2.schema.json +48 -10
- package/src/schemas/app-manifest.schema.json +1 -1
- package/src/store/plugins/liveUpdates.js +19 -9
- package/src/store/useObjectStore.js +19 -8
- package/src/utils/resolveRowActions.js +127 -0
- package/src/utils/rowActionItem.js +100 -0
- package/src/utils/timelineEvents.js +43 -0
- package/src/utils/validateManifest.js +60 -3
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/dist/nextcloud-vue.cjs.js.map
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 35437389 bytes
|
|
4
|
+
sha256: a49cebe981b616ba6555ce135455260b6b111ebcea9afc9c34da210cbc38f32b
|
package/dist/nextcloud-vue.css
CHANGED
|
@@ -1940,6 +1940,23 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
1940
1940
|
color: var(--color-text-maxcontrast);
|
|
1941
1941
|
}
|
|
1942
1942
|
|
|
1943
|
+
/* Beside the inline search (`showCountWithSearch`): the search field shrinks
|
|
1944
|
+
first and the counter stays on one line. */
|
|
1945
|
+
.cn-actions-bar__count--beside-search {
|
|
1946
|
+
flex-shrink: 0;
|
|
1947
|
+
white-space: nowrap;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
/* Taken out of the flex row so it adds no gap, but kept in the accessibility
|
|
1951
|
+
tree so the live region exists before a total appears. */
|
|
1952
|
+
.cn-actions-bar__count--empty {
|
|
1953
|
+
position: absolute;
|
|
1954
|
+
width: 1px;
|
|
1955
|
+
height: 1px;
|
|
1956
|
+
overflow: hidden;
|
|
1957
|
+
clip-path: inset(50%);
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1943
1960
|
/* Inline search field */
|
|
1944
1961
|
.cn-actions-bar__search {
|
|
1945
1962
|
position: relative;
|
|
@@ -2199,8 +2216,9 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
2199
2216
|
display: contents;
|
|
2200
2217
|
}
|
|
2201
2218
|
|
|
2202
|
-
/* #after-search filter buttons
|
|
2203
|
-
search field (DOM order — search first via its
|
|
2219
|
+
/* #after-search filter buttons, the beside-search counter and the view
|
|
2220
|
+
toggle ride beside the search field (DOM order — search first via its
|
|
2221
|
+
own order). */
|
|
2204
2222
|
.cn-actions-bar__info > * {
|
|
2205
2223
|
order: 1;
|
|
2206
2224
|
}
|
package/l10n/en.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"translations": {
|
|
3
|
+
"Advanced": "Advanced",
|
|
3
4
|
"The item could not be created.": "The item could not be created.",
|
|
4
5
|
"Start over": "Start over",
|
|
5
6
|
"Continue where you left off": "Continue where you left off",
|
|
@@ -1100,7 +1101,9 @@
|
|
|
1100
1101
|
"Show the date of today above the heading": "Show the date of today above the heading",
|
|
1101
1102
|
"Plain header without a coloured background": "Plain header without a coloured background",
|
|
1102
1103
|
"Loading…": "Loading…",
|
|
1103
|
-
"A register and schema are required": "A register and schema are required"
|
|
1104
|
+
"A register and schema are required": "A register and schema are required",
|
|
1105
|
+
"Change the search or try again.": "Change the search or try again.",
|
|
1106
|
+
"Try again later.": "Try again later."
|
|
1104
1107
|
},
|
|
1105
1108
|
"plurals": {
|
|
1106
1109
|
"%n year": ["%n year", "%n years"],
|
package/l10n/nl.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"translations": {
|
|
3
|
+
"Advanced": "Geavanceerd",
|
|
3
4
|
"The item could not be created.": "Het item kon niet worden aangemaakt.",
|
|
4
5
|
"Start over": "Opnieuw beginnen",
|
|
5
6
|
"Continue where you left off": "Ga verder waar je was",
|
|
@@ -1144,7 +1145,9 @@
|
|
|
1144
1145
|
"Show the date of today above the heading": "Toon de datum van vandaag boven de kop",
|
|
1145
1146
|
"Plain header without a coloured background": "Eenvoudige kop zonder gekleurde achtergrond",
|
|
1146
1147
|
"Loading…": "Laden…",
|
|
1147
|
-
"A register and schema are required": "Een register en schema zijn verplicht"
|
|
1148
|
+
"A register and schema are required": "Een register en schema zijn verplicht",
|
|
1149
|
+
"Change the search or try again.": "Pas de zoekopdracht aan of probeer het opnieuw.",
|
|
1150
|
+
"Try again later.": "Probeer het later opnieuw."
|
|
1148
1151
|
},
|
|
1149
1152
|
"plurals": {
|
|
1150
1153
|
"%n year": ["%n jaar", "%n jaar"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.68.0",
|
|
4
4
|
"description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
|
|
5
5
|
"homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
"CnIconPicker": 1,
|
|
112
112
|
"CnImageWidget": 1,
|
|
113
113
|
"CnImageWidgetForm": 1,
|
|
114
|
-
"CnIndexPage": 0.
|
|
114
|
+
"CnIndexPage": 0.79,
|
|
115
115
|
"CnIndexSidebar": 0.69,
|
|
116
116
|
"CnInfoWidget": 1,
|
|
117
117
|
"CnIntegrationCard": 1,
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
"CnRichSubmitDialog": 1,
|
|
203
203
|
"CnRoadmapItem": 1,
|
|
204
204
|
"CnRoadmapTab": 1,
|
|
205
|
-
"CnRowActions":
|
|
205
|
+
"CnRowActions": 1,
|
|
206
206
|
"CnSavedViewsControl": 1,
|
|
207
207
|
"CnSaveViewDialog": 1,
|
|
208
208
|
"CnSchemaFormDialog": 0.79,
|
|
@@ -152,9 +152,10 @@ Controlling the inline action button count — `inlineActionCount` sets how many
|
|
|
152
152
|
| `showMap` | `Boolean` | `false` | Whether to render the third "Map" view-toggle segment. Off by default so existing two-segment consumers are unchanged |
|
|
153
153
|
| `mapLabel` | `String` | `''` | Label for the map view-toggle option (defaults to "Map"). Only shown when `showMap` |
|
|
154
154
|
| `mapIcon` | `String` | `''` | MDI icon name for the map option (defaults to the built-in map-marker icon). Resolved via `CnIcon` |
|
|
155
|
-
| `showSearch` | `Boolean` | `false` | Whether to show the inline search field on the left of the bar |
|
|
155
|
+
| `showSearch` | `Boolean` | `false` | Whether to show the inline search field on the left of the bar. The field takes the place of the "Showing X of Y" counter unless `showCountWithSearch` is set |
|
|
156
156
|
| `searchValue` | `String` | `''` | Current value of the inline search field (controlled) |
|
|
157
157
|
| `searchPlaceholder` | `String` | `''` | Placeholder / accessible label for the inline search field |
|
|
158
|
+
| `showCountWithSearch` | `Boolean` | `false` | Keep the "Showing X of Y" counter visible beside the inline search field, after the search and any `#after-search` controls. No effect without `showSearch` |
|
|
158
159
|
| `headerActions` | `Array` | `[]` | Manifest-declared page-level actions rendered inside the overflow dropdown between the built-in Refresh and the `#action-items` slot. Each entry is `{ id, label, icon?, disabled? }` — the bar emits `@header-action({ action: id, id })` on click and the parent (e.g. `CnIndexPage`) dispatches the resolved handler. The `icon` field accepts EITHER an MDI Vue component name (e.g. `'History'`) — rendered via `CnIcon` — OR a Nextcloud core CSS icon class (e.g. `'icon-history'`) — rendered as a `<span>` carrying that class. An entry with `href` (URL) or `to` (vue-router location) renders as a real link, `linkTarget` as its `target`; it still emits `@header-action`, so the parent must not navigate for it again. |
|
|
159
160
|
|
|
160
161
|
## Manifest header actions example
|
|
@@ -12,11 +12,16 @@
|
|
|
12
12
|
:aria-label="searchPlaceholder || t('nextcloud-vue', 'Search')"
|
|
13
13
|
@input="onSearchInput">
|
|
14
14
|
</div>
|
|
15
|
-
<span v-else-if="
|
|
15
|
+
<span v-else-if="hasTotal" class="cn-actions-bar__count">
|
|
16
16
|
{{ countText }}
|
|
17
17
|
</span>
|
|
18
18
|
<!-- @slot after-search Refinement controls rendered beside the search field on the LEFT side of the bar (e.g. a filter menu button). Convention: the left side groups the VISUAL controls — search, filters, view toggle — while the right cluster holds the ACT controls (add, overflow); the standalone sort select is a display control too but keeps its legacy right-side placement. -->
|
|
19
19
|
<slot name="after-search" />
|
|
20
|
+
<!-- Counter beside the search (`showCountWithSearch`): after the search and its #after-search controls. The live region stays mounted while there is no total, so results coming back after none are announced too. -->
|
|
21
|
+
<span v-if="showSearch && showCountWithSearch"
|
|
22
|
+
class="cn-actions-bar__count cn-actions-bar__count--beside-search"
|
|
23
|
+
:class="{ 'cn-actions-bar__count--empty': !hasTotal }"
|
|
24
|
+
aria-live="polite">{{ hasTotal ? countText : '' }}</span>
|
|
20
25
|
|
|
21
26
|
<!-- View mode toggle (Cards / Table / List) — segmented control with
|
|
22
27
|
a sliding thumb that animates between the N segments. Lives in
|
|
@@ -644,6 +649,16 @@ export default {
|
|
|
644
649
|
default: '',
|
|
645
650
|
},
|
|
646
651
|
|
|
652
|
+
/**
|
|
653
|
+
* Keep the "Showing X of Y" counter visible beside the inline search field,
|
|
654
|
+
* after any `#after-search` controls. By default the search field takes the
|
|
655
|
+
* counter's place. No effect without `showSearch`.
|
|
656
|
+
*/
|
|
657
|
+
showCountWithSearch: {
|
|
658
|
+
type: Boolean,
|
|
659
|
+
default: false,
|
|
660
|
+
},
|
|
661
|
+
|
|
647
662
|
/** Whether the refresh action is currently in progress */
|
|
648
663
|
refreshing: {
|
|
649
664
|
type: Boolean,
|
|
@@ -811,6 +826,15 @@ export default {
|
|
|
811
826
|
}))
|
|
812
827
|
},
|
|
813
828
|
|
|
829
|
+
/**
|
|
830
|
+
* Whether there is a non-empty total for the "Showing X of Y" counter.
|
|
831
|
+
*
|
|
832
|
+
* @return {boolean}
|
|
833
|
+
*/
|
|
834
|
+
hasTotal() {
|
|
835
|
+
return Boolean(this.pagination && this.pagination.total > 0)
|
|
836
|
+
},
|
|
837
|
+
|
|
814
838
|
countText() {
|
|
815
839
|
if (!this.pagination) {
|
|
816
840
|
return ''
|
|
@@ -1113,7 +1113,7 @@ export default {
|
|
|
1113
1113
|
|
|
1114
1114
|
/**
|
|
1115
1115
|
* Label for the foldout's gear button. Manifest override:
|
|
1116
|
-
* `nav.settingsLabel`; defaults to "
|
|
1116
|
+
* `nav.settingsLabel`; defaults to "Advanced".
|
|
1117
1117
|
*
|
|
1118
1118
|
* @return {string}
|
|
1119
1119
|
*/
|
|
@@ -1122,7 +1122,7 @@ export default {
|
|
|
1122
1122
|
if (typeof custom === 'string' && custom.length > 0) {
|
|
1123
1123
|
return this.effectiveTranslate(custom)
|
|
1124
1124
|
}
|
|
1125
|
-
return t('nextcloud-vue', '
|
|
1125
|
+
return t('nextcloud-vue', 'Advanced')
|
|
1126
1126
|
},
|
|
1127
1127
|
|
|
1128
1128
|
/**
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
@close="onClose"
|
|
13
13
|
@closed="onClosed">
|
|
14
14
|
<!-- Dynamic actions from array prop -->
|
|
15
|
-
<template v-for="{ action, link } in renderedActions" :key="action
|
|
15
|
+
<template v-for="{ action, link } in renderedActions" :key="actionKey(action)">
|
|
16
16
|
<!-- A navigate-only action is a real link, so it can be middle-clicked, opened in a new tab or copied. -->
|
|
17
17
|
<NcActionLink
|
|
18
18
|
v-if="link"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
:target="link.target"
|
|
21
21
|
:title="resolveTitle(action)"
|
|
22
22
|
:class="{ 'cn-row-action--destructive': action.destructive }"
|
|
23
|
-
:data-testid="
|
|
23
|
+
:data-testid="actionTestId(action)"
|
|
24
24
|
closeAfterClick
|
|
25
25
|
@click="onLinkAction(action, link, $event)">
|
|
26
26
|
<template v-if="action.icon" #icon>
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
:title="resolveTitle(action)"
|
|
35
35
|
:disabled="resolveDisabled(action)"
|
|
36
36
|
:class="{ 'cn-row-action--destructive': action.destructive }"
|
|
37
|
-
:data-testid="
|
|
37
|
+
:data-testid="actionTestId(action)"
|
|
38
38
|
closeAfterClick
|
|
39
39
|
@click="onAction(action)">
|
|
40
40
|
<template v-if="action.icon" #icon>
|
|
@@ -95,6 +95,7 @@ import { NcActionButton, NcActionLink, NcActions } from '@nextcloud/vue'
|
|
|
95
95
|
import { CTX_MENU_DATA_ATTR, CTX_MENU_POPPER_ATTR } from '../../composables/useContextMenu.js'
|
|
96
96
|
import { followItemActionLink, resolveItemActionLink } from '../../utils/actionLink.js'
|
|
97
97
|
import { isModifiedClick } from '../../utils/linkNavigation.js'
|
|
98
|
+
import { isRowActionVisible, rowActionKey, rowActionPayload, rowActionTestId, slugifyActionLabel } from '../../utils/rowActionItem.js'
|
|
98
99
|
import { CnIcon } from '../CnIcon/index.js'
|
|
99
100
|
|
|
100
101
|
/**
|
|
@@ -109,12 +110,13 @@ import { CnIcon } from '../CnIcon/index.js'
|
|
|
109
110
|
* cursor positioning). The composable handles the DOM attributes; this component
|
|
110
111
|
* handles the NcActions template boilerplate.
|
|
111
112
|
*
|
|
112
|
-
* Dynamic actions (CnIndexPage pattern)
|
|
113
|
+
* Dynamic actions (CnIndexPage pattern).
|
|
114
|
+
* CnIndexPage passes the same per-row list its row actions menu renders, so the two menus always match.
|
|
113
115
|
* ```vue
|
|
114
116
|
* <CnContextMenu
|
|
115
117
|
* v-model:open="contextMenuOpen"
|
|
116
|
-
* :actions="
|
|
117
|
-
* :target-item="
|
|
118
|
+
* :actions="rowActionsFor(contextMenuShownRow)"
|
|
119
|
+
* :target-item="contextMenuShownRow"
|
|
118
120
|
* @action="$emit('action', $event)"
|
|
119
121
|
* @close="closeContextMenu" />
|
|
120
122
|
* ```
|
|
@@ -161,13 +163,15 @@ export default {
|
|
|
161
163
|
* `visible` (boolean | (targetItem) => boolean) hides the entry when falsy
|
|
162
164
|
* (default: shown). `title` (string | (targetItem) => string) renders as
|
|
163
165
|
* a native tooltip — useful for explaining why an entry is disabled.
|
|
166
|
+
* A locally decidable `visibleWhen` that is false for the target hides the entry too, as in CnRowActions.
|
|
167
|
+
* `id` is passed in the `action` payload; `builtin` marks a CnIndexPage built-in, whose testid is `cn-action-item-<id>` in every locale.
|
|
164
168
|
* `href` (URL) or `to` (vue-router location), each a value or a
|
|
165
169
|
* `(targetItem) => …` function, render the entry as a real link, with
|
|
166
170
|
* `linkTarget` as its `target`; a link emits `action` but does not call
|
|
167
171
|
* `handler`. When the entire array is empty (or all entries are filtered
|
|
168
172
|
* out), only the default slot content is rendered.
|
|
169
173
|
*
|
|
170
|
-
* @type {Array<{label: string, icon: object | string, handler: (targetItem: object) => void, disabled: boolean | ((targetItem: object) => boolean), visible: boolean | ((targetItem: object) => boolean), title: string | ((targetItem: object) => string), destructive: boolean, href: string | ((targetItem: object) => string), to: string | object | ((targetItem: object) => string | object), linkTarget: string}>}
|
|
174
|
+
* @type {Array<{label: string, id: string, builtin: boolean, icon: object | string, handler: (targetItem: object) => void, disabled: boolean | ((targetItem: object) => boolean), visible: boolean | ((targetItem: object) => boolean), visibleWhen: object, title: string | ((targetItem: object) => string), destructive: boolean, href: string | ((targetItem: object) => string), to: string | object | ((targetItem: object) => string | object), linkTarget: string}>}
|
|
171
175
|
*/
|
|
172
176
|
actions: {
|
|
173
177
|
type: Array,
|
|
@@ -209,21 +213,13 @@ export default {
|
|
|
209
213
|
|
|
210
214
|
computed: {
|
|
211
215
|
/**
|
|
212
|
-
* Filter actions by their `visible`
|
|
213
|
-
*
|
|
216
|
+
* Filter actions by their `visibleWhen` and `visible` gates, with the same rule CnRowActions uses.
|
|
217
|
+
* Entries without either always show.
|
|
214
218
|
*
|
|
215
219
|
* @return {Array} Visible actions for the current targetItem.
|
|
216
220
|
*/
|
|
217
221
|
visibleActions() {
|
|
218
|
-
return this.actions.filter((action) =>
|
|
219
|
-
if (action.visible === undefined) {
|
|
220
|
-
return true
|
|
221
|
-
}
|
|
222
|
-
if (typeof action.visible === 'function') {
|
|
223
|
-
return !!action.visible(this.targetItem)
|
|
224
|
-
}
|
|
225
|
-
return !!action.visible
|
|
226
|
-
})
|
|
222
|
+
return this.actions.filter((action) => isRowActionVisible(action, this.targetItem))
|
|
227
223
|
},
|
|
228
224
|
|
|
229
225
|
/**
|
|
@@ -466,18 +462,33 @@ export default {
|
|
|
466
462
|
|
|
467
463
|
/**
|
|
468
464
|
* Slugify an action label for use in stable `data-testid` selectors.
|
|
469
|
-
* Lowercase, kebab-case, strip non-alphanumeric.
|
|
470
|
-
* `:data-testid` binding on NcActionButton — does not affect runtime
|
|
471
|
-
* behaviour or rendered text.
|
|
465
|
+
* Lowercase, kebab-case, strip non-alphanumeric.
|
|
472
466
|
*
|
|
473
467
|
* @param {string} label The action's display label.
|
|
474
468
|
* @return {string} kebab-case slug suitable for a testid suffix.
|
|
475
469
|
*/
|
|
476
470
|
slugifyLabel(label) {
|
|
477
|
-
return
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
471
|
+
return slugifyActionLabel(label)
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* The entry's `data-testid`: `cn-action-item-<id>` for a built-in, the label slug otherwise.
|
|
476
|
+
*
|
|
477
|
+
* @param {object} action The action descriptor.
|
|
478
|
+
* @return {string} The testid.
|
|
479
|
+
*/
|
|
480
|
+
actionTestId(action) {
|
|
481
|
+
return rowActionTestId(action)
|
|
482
|
+
},
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* The entry's render key: `builtin:<id>` for a built-in, the label otherwise.
|
|
486
|
+
*
|
|
487
|
+
* @param {object} action The action descriptor.
|
|
488
|
+
* @return {string} The key.
|
|
489
|
+
*/
|
|
490
|
+
actionKey(action) {
|
|
491
|
+
return rowActionKey(action)
|
|
481
492
|
},
|
|
482
493
|
|
|
483
494
|
onAction(action) {
|
|
@@ -485,10 +496,10 @@ export default {
|
|
|
485
496
|
action.handler(this.targetItem)
|
|
486
497
|
}
|
|
487
498
|
/**
|
|
488
|
-
* @event action User picked an entry from the menu. The action's own `handler(targetItem)` (when present) ran synchronously before this event fires; the event lets parents observe / log the choice.
|
|
489
|
-
* @type {{ action: string, row: object|null }}
|
|
499
|
+
* @event action User picked an entry from the menu. The action's own `handler(targetItem)` (when present) ran synchronously before this event fires; the event lets parents observe / log the choice. Payload: `action` (the label), the target as `row`, the action's `id` when it has one, and `builtin: true` for a CnIndexPage built-in.
|
|
500
|
+
* @type {{ action: string, row: object|null, id?: string, builtin?: boolean }}
|
|
490
501
|
*/
|
|
491
|
-
this.$emit('action',
|
|
502
|
+
this.$emit('action', rowActionPayload(action, this.targetItem))
|
|
492
503
|
},
|
|
493
504
|
|
|
494
505
|
/**
|
|
@@ -506,7 +517,7 @@ export default {
|
|
|
506
517
|
return
|
|
507
518
|
}
|
|
508
519
|
followItemActionLink(event, link, this.$router)
|
|
509
|
-
this.$emit('action',
|
|
520
|
+
this.$emit('action', rowActionPayload(action, this.targetItem))
|
|
510
521
|
},
|
|
511
522
|
|
|
512
523
|
onClose() {
|
|
@@ -186,7 +186,7 @@ export default {
|
|
|
186
186
|
| `includeColumns` | Array | `null` | Column keys to include (whitelist) in schema mode |
|
|
187
187
|
| `sortKey` | String | `null` | Currently active sort column key (controlled) |
|
|
188
188
|
| `sortOrder` | String | `'asc'` | Current sort direction: `'asc'` or `'desc'` |
|
|
189
|
-
| `sortKeys` | Array | `[]` | Ordered multi-column ("shift+click") sort key list, `[{ key, order }, …]` (0–3 entries); takes precedence over `sortKey`/`sortOrder` when non-empty. Priority badges show only when two or more rendered, sortable columns are sorted; a key whose column is not rendered or not sortable (such as a `_uuid` tie-break) still sorts, but is not counted or numbered |
|
|
189
|
+
| `sortKeys` | Array | `[]` | Ordered multi-column ("shift+click") sort key list, `[{ key, order }, …]` (0–3 entries); takes precedence over `sortKey`/`sortOrder` when non-empty. Priority badges show only when two or more rendered, sortable columns are sorted; a key whose column is not rendered or not sortable (such as a `_uuid` tie-break) still sorts, but is not counted or numbered, and `aria-sort` goes to the first sort key whose column is rendered and sortable |
|
|
190
190
|
| `selectedIds` | Array | `[]` | Array of selected row IDs (controlled) |
|
|
191
191
|
| `rowClass` | Function | `null` | `(row) => string` — CSS class(es) applied to each `<tr>` |
|
|
192
192
|
| `rowClickToView` | Boolean | `false` | Emit `row-click` on a row-body click even while `selectable` (selection then via the checkbox column only) |
|
|
@@ -371,7 +371,7 @@ import '../../css/table.css'
|
|
|
371
371
|
*
|
|
372
372
|
* Sorting: a plain click on a sortable header is single-sort (cycle asc → desc → cleared), unchanged from before.
|
|
373
373
|
* Shift+click (or Shift+Enter on a focused header) appends the column as a secondary/tertiary sort key, capped at 3, with numbered priority badges (1, 2, 3) once two or more rendered, sortable columns are sorted.
|
|
374
|
-
* A sort key whose column is not rendered, or not sortable, still sorts, but is not counted or numbered.
|
|
374
|
+
* A sort key whose column is not rendered, or not sortable, still sorts, but is not counted or numbered, and `aria-sort` goes to the first sort key whose column is rendered and sortable.
|
|
375
375
|
* Pass `sortKeys: [{key, order}, ...]` for multi-sort (falls back to the legacy `sortKey`/`sortOrder` props when empty).
|
|
376
376
|
* The `sort` event payload is extended, not replaced: `{key, order}` still mirrors the primary key exactly as before; a new `keys` field carries the full ordered list.
|
|
377
377
|
* See `src/utils/multiColumnSort.js` for the state machine.
|
|
@@ -1722,8 +1722,8 @@ export default {
|
|
|
1722
1722
|
},
|
|
1723
1723
|
|
|
1724
1724
|
/**
|
|
1725
|
-
* `aria-sort` value for a column header: `'ascending'`/`'descending'` for the
|
|
1726
|
-
* `null` omits the attribute entirely (unsorted, not sortable, or not the
|
|
1725
|
+
* `aria-sort` value for a column header: `'ascending'`/`'descending'` for the first rendered sort key only, per WCAG guidance that `aria-sort` describes single-column sort state.
|
|
1726
|
+
* `null` omits the attribute entirely (unsorted, not sortable, or not the first rendered sort key).
|
|
1727
1727
|
*
|
|
1728
1728
|
* @param {object} col Column definition.
|
|
1729
1729
|
* @return {string|null}
|
|
@@ -1732,7 +1732,7 @@ export default {
|
|
|
1732
1732
|
if (!col.sortable) {
|
|
1733
1733
|
return null
|
|
1734
1734
|
}
|
|
1735
|
-
const primary = this.
|
|
1735
|
+
const primary = this.renderedSortKeys[0]
|
|
1736
1736
|
if (!primary || primary.key !== col.key) {
|
|
1737
1737
|
return null
|
|
1738
1738
|
}
|
|
@@ -462,6 +462,11 @@
|
|
|
462
462
|
:register="nestedCreate.register"
|
|
463
463
|
:item="null"
|
|
464
464
|
:initialData="nestedCreate.initialData"
|
|
465
|
+
:excludeFields="nestedCreate.form.excludeFields"
|
|
466
|
+
:includeFields="nestedCreate.form.includeFields"
|
|
467
|
+
:fieldOverrides="nestedCreate.form.fieldOverrides"
|
|
468
|
+
:size="nestedCreate.form.size"
|
|
469
|
+
:columns="nestedCreate.form.columns"
|
|
465
470
|
:recoverDraft="false"
|
|
466
471
|
data-testid="cn-form-dialog-nested-create"
|
|
467
472
|
@confirm="onNestedCreateConfirm"
|
|
@@ -2645,7 +2650,12 @@ export default {
|
|
|
2645
2650
|
* the new object. When the referenced schema cannot be loaded, the
|
|
2646
2651
|
* term alone is saved as before, so the field never dead-ends.
|
|
2647
2652
|
*
|
|
2653
|
+
* The form is the one the schema's index page shows for its own Add
|
|
2654
|
+
* button: that page's `createOverride` saves it and its form settings
|
|
2655
|
+
* shape it, with the typed term in the name.
|
|
2656
|
+
*
|
|
2648
2657
|
* @spec openspec/changes/form-pickers-from-schema/specs/schema-utilities/spec.md
|
|
2658
|
+
* @spec openspec/changes/nested-create-uses-page-config/specs/schema-utilities/spec.md
|
|
2649
2659
|
* @param {object} field The reference field descriptor.
|
|
2650
2660
|
* @param {string} term The typed search term.
|
|
2651
2661
|
* @return {Promise<object|null>} The created object, or null.
|
|
@@ -2662,21 +2672,37 @@ export default {
|
|
|
2662
2672
|
store.registerObjectType(slug, schemaRef, register)
|
|
2663
2673
|
}
|
|
2664
2674
|
const labelField = this.referenceLabelField(field)
|
|
2675
|
+
const loadSchema = async () => {
|
|
2676
|
+
try {
|
|
2677
|
+
return await store.fetchSchema(slug)
|
|
2678
|
+
} catch {
|
|
2679
|
+
return null
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2665
2682
|
// The app may own how this schema is created (pipelinq's client and
|
|
2666
2683
|
// contact need a contact-first endpoint; a plain save 400s). A
|
|
2667
2684
|
// create override keeps this form and saves through the app's
|
|
2668
2685
|
// handler; a create modal (only when no override exists) opens the
|
|
2669
2686
|
// app's own dialog instead of the generic form.
|
|
2670
|
-
|
|
2687
|
+
//
|
|
2688
|
+
// A stored schema usually names its reference by id (`$ref: 28`)
|
|
2689
|
+
// while the manifest page names it by slug (`schema: 'client'`), so
|
|
2690
|
+
// when the reference itself matches no page, the referenced schema
|
|
2691
|
+
// is loaded and its slug, id and uuid are tried too.
|
|
2692
|
+
let schema
|
|
2693
|
+
let appCreate = this.appCreateFor(register, [schemaRef])
|
|
2694
|
+
if (!appCreate.page) {
|
|
2695
|
+
schema = await loadSchema()
|
|
2696
|
+
if (schema) {
|
|
2697
|
+
appCreate = this.appCreateFor(register, [schemaRef, schema.slug, schema.id, schema.uuid])
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2671
2700
|
if (!appCreate.override && appCreate.modal) {
|
|
2672
2701
|
return this.openAppCreateModal(appCreate.modal, store, slug, labelField, term)
|
|
2673
2702
|
}
|
|
2674
2703
|
const override = appCreate.override
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
schema = await store.fetchSchema(slug)
|
|
2678
|
-
} catch {
|
|
2679
|
-
schema = null
|
|
2704
|
+
if (schema === undefined) {
|
|
2705
|
+
schema = await loadSchema()
|
|
2680
2706
|
}
|
|
2681
2707
|
if (!schema || !schema.properties) {
|
|
2682
2708
|
const payload = { [labelField]: term }
|
|
@@ -2685,36 +2711,84 @@ export default {
|
|
|
2685
2711
|
: store.saveObject(slug, payload)
|
|
2686
2712
|
}
|
|
2687
2713
|
const initialData = {}
|
|
2688
|
-
|
|
2689
|
-
|
|
2714
|
+
const prefillKey = this.nestedPrefillKey(schema, labelField)
|
|
2715
|
+
if (term && prefillKey) {
|
|
2716
|
+
initialData[prefillKey] = term
|
|
2690
2717
|
}
|
|
2718
|
+
const form = appCreate.form || this.nestedFormConfig(null)
|
|
2691
2719
|
return new Promise((resolve) => {
|
|
2692
|
-
this.nestedCreate = { field, schema, register, slug, schemaRef, initialData, override, resolve }
|
|
2720
|
+
this.nestedCreate = { field, schema, register, slug, schemaRef, initialData, override, form, resolve }
|
|
2693
2721
|
})
|
|
2694
2722
|
},
|
|
2695
2723
|
|
|
2724
|
+
/**
|
|
2725
|
+
* The property of the referenced schema the typed term belongs in: the
|
|
2726
|
+
* reference's label field when the schema has it, else its name, title
|
|
2727
|
+
* or label. Visibility is decided by the form (a page `fieldOverrides`
|
|
2728
|
+
* entry can make a read-only name editable), not here.
|
|
2729
|
+
*
|
|
2730
|
+
* @spec openspec/changes/nested-create-uses-page-config/specs/schema-utilities/spec.md
|
|
2731
|
+
* @param {object} schema The referenced schema.
|
|
2732
|
+
* @param {string} labelField The reference's label field.
|
|
2733
|
+
* @return {string|null} The property key, or null when none fits.
|
|
2734
|
+
*/
|
|
2735
|
+
nestedPrefillKey(schema, labelField) {
|
|
2736
|
+
const properties = (schema && schema.properties) || {}
|
|
2737
|
+
return [labelField, 'name', 'title', 'label'].find((key) => properties[key]) || null
|
|
2738
|
+
},
|
|
2739
|
+
|
|
2740
|
+
/**
|
|
2741
|
+
* The create-form settings of the index page that lists a schema, in
|
|
2742
|
+
* the shape the nested CnFormDialog binds: the same `excludeFields`,
|
|
2743
|
+
* `includeFields`, `fieldOverrides`, `formSize` and `formColumns`
|
|
2744
|
+
* CnIndexPage hands its own Add form, so creating from a picker shows
|
|
2745
|
+
* the form the page shows. Without a page, the form's defaults.
|
|
2746
|
+
*
|
|
2747
|
+
* @spec openspec/changes/nested-create-uses-page-config/specs/schema-utilities/spec.md
|
|
2748
|
+
* @param {object|null} config The page's `config`, or null.
|
|
2749
|
+
* @return {{excludeFields: string[], includeFields: (string[]|null), fieldOverrides: object, size: string, columns: number}} The form settings.
|
|
2750
|
+
*/
|
|
2751
|
+
nestedFormConfig(config) {
|
|
2752
|
+
const c = config || {}
|
|
2753
|
+
const isMap = (v) => v !== null && typeof v === 'object' && !Array.isArray(v)
|
|
2754
|
+
return {
|
|
2755
|
+
excludeFields: Array.isArray(c.excludeFields) ? c.excludeFields : [],
|
|
2756
|
+
includeFields: Array.isArray(c.includeFields) ? c.includeFields : null,
|
|
2757
|
+
fieldOverrides: isMap(c.fieldOverrides) ? c.fieldOverrides : {},
|
|
2758
|
+
size: typeof c.formSize === 'string' && c.formSize !== '' ? c.formSize : 'normal',
|
|
2759
|
+
columns: c.formColumns === 2 ? 2 : 1,
|
|
2760
|
+
}
|
|
2761
|
+
},
|
|
2762
|
+
|
|
2696
2763
|
/**
|
|
2697
2764
|
* How the app creates objects of a schema, read off the manifest page
|
|
2698
2765
|
* that lists them: the index page whose `config.schema` (and, when it
|
|
2699
2766
|
* names one, `config.register`) matches. Its `config.createOverride` is
|
|
2700
2767
|
* resolved to the registered handler; its `config.createModal` is the
|
|
2701
|
-
* registry key of a `kind: 'modal'` dialog
|
|
2768
|
+
* registry key of a `kind: 'modal'` dialog; its form settings
|
|
2769
|
+
* (`fieldOverrides` and friends) shape the nested form.
|
|
2702
2770
|
*
|
|
2703
|
-
*
|
|
2771
|
+
* The schema is matched on any of the given references, so a caller
|
|
2772
|
+
* holding the referenced schema can pass its slug, id and uuid: the
|
|
2773
|
+
* manifest names a schema by slug, a stored `$ref` often by id.
|
|
2774
|
+
*
|
|
2775
|
+
* @spec openspec/changes/nested-create-uses-page-config/specs/schema-utilities/spec.md
|
|
2704
2776
|
* @param {string} register The reference's register.
|
|
2705
|
-
* @param {string}
|
|
2706
|
-
* @return {{override: (((formData: object, ctx: object) => Promise<object>)|null), modal: (string|null)}} What the app declared.
|
|
2777
|
+
* @param {string|Array<string|number>} schemaRefs The schema's slug, id or uuid, one or several.
|
|
2778
|
+
* @return {{page: (object|null), override: (((formData: object, ctx: object) => Promise<object>)|null), modal: (string|null), form: (object|null)}} What the app declared.
|
|
2707
2779
|
*/
|
|
2708
|
-
appCreateFor(register,
|
|
2709
|
-
const none = { override: null, modal: null }
|
|
2780
|
+
appCreateFor(register, schemaRefs) {
|
|
2781
|
+
const none = { page: null, override: null, modal: null, form: null }
|
|
2710
2782
|
const pages = this.cnManifest && Array.isArray(this.cnManifest.pages) ? this.cnManifest.pages : []
|
|
2711
|
-
const wanted =
|
|
2783
|
+
const wanted = (Array.isArray(schemaRefs) ? schemaRefs : [schemaRefs])
|
|
2784
|
+
.filter((ref) => ref !== undefined && ref !== null && ref !== '')
|
|
2785
|
+
.map((ref) => String(ref).toLowerCase())
|
|
2712
2786
|
const page = pages.find((p) => {
|
|
2713
2787
|
const config = p && p.config
|
|
2714
2788
|
if (!config || config.schema === undefined || config.schema === null) {
|
|
2715
2789
|
return false
|
|
2716
2790
|
}
|
|
2717
|
-
if (String(config.schema).toLowerCase()
|
|
2791
|
+
if (!wanted.includes(String(config.schema).toLowerCase())) {
|
|
2718
2792
|
return false
|
|
2719
2793
|
}
|
|
2720
2794
|
return !config.register || String(config.register) === String(register)
|
|
@@ -2732,7 +2806,7 @@ export default {
|
|
|
2732
2806
|
const modal = modalEntry && modalEntry.kind === 'modal' && typeof this.cnOpenModal === 'function'
|
|
2733
2807
|
? config.createModal
|
|
2734
2808
|
: null
|
|
2735
|
-
return { override: override || null, modal }
|
|
2809
|
+
return { page, override: override || null, modal, form: this.nestedFormConfig(config) }
|
|
2736
2810
|
},
|
|
2737
2811
|
|
|
2738
2812
|
/**
|
|
@@ -386,7 +386,8 @@ export default {
|
|
|
386
386
|
| `fieldOverrides` | Object | `{}` | Per-field config overrides passed to `CnFormDialog` |
|
|
387
387
|
| `customComponents` | Object | `null` | Custom-component / handler registry. When set, takes precedence over the injected `cnCustomComponents` from CnAppRoot. Used to resolve `actions[].handler` registry names declared in the manifest (manifest-actions-dispatch). Named handlers resolve out of the v2 `registry` (a `kind: "handler"` entry) FIRST and fall back to this map. |
|
|
388
388
|
| `showViewToggle` | Boolean | `true` | Whether to show the Cards/Table view toggle |
|
|
389
|
-
| `inlineSearch` | Boolean | `false` | Show an inline search field in the actions bar (in addition to / instead of the sidebar search). Fed from the manifest as `pages[].config.inlineSearch`. |
|
|
389
|
+
| `inlineSearch` | Boolean | `false` | Show an inline search field in the actions bar (in addition to / instead of the sidebar search). Fed from the manifest as `pages[].config.inlineSearch`. The field takes the place of the "Showing X of Y" counter unless `showCountWithSearch` is set. |
|
|
390
|
+
| `showCountWithSearch` | Boolean | `false` | Keep the "Showing X of Y" counter visible beside the inline search field, after the search and any `#after-search` controls (only relevant with `inlineSearch`, which otherwise takes the counter's place). Fed from the manifest as `pages[].config.showCountWithSearch`. |
|
|
390
391
|
| `searchPlaceholder` | String | `''` | Placeholder for the inline search field (manifest `config.searchPlaceholder`). |
|
|
391
392
|
| `cardsLabel` | String | `''` | Label for the cards view-toggle option (manifest `config.cardsLabel`, e.g. "Tiles"). |
|
|
392
393
|
| `tableLabel` | String | `''` | Label for the table view-toggle option (manifest `config.tableLabel`, e.g. "List"). |
|
|
@@ -865,6 +866,27 @@ actions, and the page's declaration stands as before. Wire the field on one
|
|
|
865
866
|
list first, then check `rowActionsNotDeclared` to see what the server is
|
|
866
867
|
offering that you have not declared yet.
|
|
867
868
|
|
|
869
|
+
The built-in View, Edit, Copy and Delete carry the ids `view`, `edit`, `copy` and `delete`, and match this block by id only, never by their label.
|
|
870
|
+
The right-click menu reads the same per-row list as the actions menu, so it drops the same refused actions.
|
|
871
|
+
|
|
872
|
+
## Placing built-in row actions
|
|
873
|
+
|
|
874
|
+
A `"builtin:view"`, `"builtin:edit"`, `"builtin:copy"` or `"builtin:delete"` string in `actions` puts that built-in at its position; enabled built-ins the array does not place are appended in the default order, and the `show*Action` toggles still decide whether each renders.
|
|
875
|
+
|
|
876
|
+
```json
|
|
877
|
+
"actionToggles": { "showViewAction": false },
|
|
878
|
+
"actions": [
|
|
879
|
+
"builtin:edit",
|
|
880
|
+
"builtin:copy",
|
|
881
|
+
{ "id": "file-list", "label": "File list", "handler": "openPublicationFiles" },
|
|
882
|
+
"builtin:delete"
|
|
883
|
+
]
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
This renders Edit, Copy, File list, Delete, and makes Edit the keyboard primary action.
|
|
887
|
+
The keyboard primary action is the first entry the menu shows and enables, so a hidden or disabled entry is skipped.
|
|
888
|
+
See the CnIndexPage docs page for the full rules.
|
|
889
|
+
|
|
868
890
|
## State indicators on a row
|
|
869
891
|
|
|
870
892
|
Declare `rowIndicators` and a row carries the flags a handler triages on. Each
|