@conduction/nextcloud-vue 2.67.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.
Files changed (55) hide show
  1. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +37 -26
  2. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
  3. package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js +3 -3
  4. package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
  5. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +87 -18
  6. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  7. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +6 -1
  8. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  9. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +58 -48
  10. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  11. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -2
  12. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  13. package/dist/esm/components/CnIndexPage/defaultActions.js +6 -7
  14. package/dist/esm/components/CnIndexPage/defaultActions.js.map +1 -1
  15. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +33 -34
  16. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
  17. package/dist/esm/components/CnRowActions/CnRowActions.vue3.js +3 -3
  18. package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
  19. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +8 -2
  20. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  21. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  22. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +3 -2
  23. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -1
  24. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -1
  25. package/dist/esm/composables/useContextMenu.js +4 -2
  26. package/dist/esm/composables/useContextMenu.js.map +1 -1
  27. package/dist/esm/utils/resolveRowActions.js +130 -0
  28. package/dist/esm/utils/resolveRowActions.js.map +1 -0
  29. package/dist/esm/utils/rowActionItem.js +104 -0
  30. package/dist/esm/utils/rowActionItem.js.map +1 -0
  31. package/dist/esm/utils/timelineEvents.js +44 -1
  32. package/dist/esm/utils/timelineEvents.js.map +1 -1
  33. package/dist/esm/utils/validateManifest.js +59 -3
  34. package/dist/esm/utils/validateManifest.js.map +1 -1
  35. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  36. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  37. package/dist/nextcloud-vue.cjs.js +575 -151
  38. package/dist/nextcloud-vue.cjs.js.map +2 -2
  39. package/package.json +1 -1
  40. package/scripts/.jsdoc-baselines.json +2 -2
  41. package/src/components/CnContextMenu/CnContextMenu.vue +40 -29
  42. package/src/components/CnFormDialog/CnFormDialog.vue +92 -18
  43. package/src/components/CnIndexPage/CnIndexPage.md +21 -0
  44. package/src/components/CnIndexPage/CnIndexPage.vue +61 -51
  45. package/src/components/CnIndexPage/defaultActions.js +6 -7
  46. package/src/components/CnRowActions/CnRowActions.vue +36 -37
  47. package/src/components/CnSetupWizard/CnSetupWizard.vue +8 -2
  48. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +3 -2
  49. package/src/composables/useContextMenu.js +4 -2
  50. package/src/schemas/app-manifest-v2.schema.json +46 -8
  51. package/src/utils/resolveRowActions.js +127 -0
  52. package/src/utils/rowActionItem.js +100 -0
  53. package/src/utils/timelineEvents.js +43 -0
  54. package/src/utils/validateManifest.js +60 -3
  55. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -14,9 +14,8 @@ import TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue';
14
14
  * without it, or when it returns null, View is a button.
15
15
  * @param {{ onView: (row: object) => void, onEdit: (row: object) => void, onCopy: (row: object) => void, onDelete: (row: object) => void }} opts.handlers
16
16
  * Click handlers bound to each emitted action, in the same order as `flags`.
17
- * @return {Array<object>} The enabled actions in menu order (view, edit, copy,
18
- * delete), each `{label, icon, handler}` — delete additionally `destructive`,
19
- * view additionally `to` when `viewTo` is given.
17
+ * @return {Array<object>} The enabled actions in menu order (view, edit, copy, delete), each `{id, builtin: true, label, icon, handler}`.
18
+ * The id is `view`, `edit`, `copy` or `delete`; delete additionally carries `destructive`, view additionally `to` when `viewTo` is given.
20
19
  */
21
20
  function buildDefaultActions({ flags, viewIcon, viewTo = null, handlers }) {
22
21
  // t() at BUILD-actions time, not module time: the labels were string
@@ -26,20 +25,20 @@ function buildDefaultActions({ flags, viewIcon, viewTo = null, handlers }) {
26
25
  // consuming app's language is already resolved.
27
26
  const out = [];
28
27
  if (flags.view) {
29
- const view = { label: translate('nextcloud-vue', 'View'), icon: viewIcon, handler: handlers.onView };
28
+ const view = { id: 'view', builtin: true, label: translate('nextcloud-vue', 'View'), icon: viewIcon, handler: handlers.onView };
30
29
  if (typeof viewTo === 'function') {
31
30
  view.to = viewTo;
32
31
  }
33
32
  out.push(view);
34
33
  }
35
34
  if (flags.edit) {
36
- out.push({ label: translate('nextcloud-vue', 'Edit'), icon: Pencil, handler: handlers.onEdit });
35
+ out.push({ id: 'edit', builtin: true, label: translate('nextcloud-vue', 'Edit'), icon: Pencil, handler: handlers.onEdit });
37
36
  }
38
37
  if (flags.copy) {
39
- out.push({ label: translate('nextcloud-vue', 'Copy'), icon: ContentCopy, handler: handlers.onCopy });
38
+ out.push({ id: 'copy', builtin: true, label: translate('nextcloud-vue', 'Copy'), icon: ContentCopy, handler: handlers.onCopy });
40
39
  }
41
40
  if (flags.del) {
42
- out.push({ label: translate('nextcloud-vue', 'Delete'), icon: TrashCanOutline, destructive: true, handler: handlers.onDelete });
41
+ out.push({ id: 'delete', builtin: true, label: translate('nextcloud-vue', 'Delete'), icon: TrashCanOutline, destructive: true, handler: handlers.onDelete });
43
42
  }
44
43
  return out
45
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"defaultActions.js","sources":["../../../../src/components/CnIndexPage/defaultActions.js"],"sourcesContent":["import { translate as t } from '@nextcloud/l10n'\nimport ContentCopy from 'vue-material-design-icons/ContentCopy.vue'\nimport Pencil from 'vue-material-design-icons/Pencil.vue'\nimport TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'\n\n/**\n * Build CnIndexPage's built-in row actions from the show*Action flags.\n *\n * @param {object} opts The action-building options.\n * @param {{ view: boolean, edit: boolean, copy: boolean, del: boolean }} opts.flags\n * Which built-in actions to include, from the page's `show*Action` props.\n * @param {object} opts.viewIcon Component to use as the View action icon.\n * @param {((row: object) => object|null)|null} [opts.viewTo] Where View links to;\n * without it, or when it returns null, View is a button.\n * @param {{ onView: (row: object) => void, onEdit: (row: object) => void, onCopy: (row: object) => void, onDelete: (row: object) => void }} opts.handlers\n * Click handlers bound to each emitted action, in the same order as `flags`.\n * @return {Array<object>} The enabled actions in menu order (view, edit, copy,\n * delete), each `{label, icon, handler}` — delete additionally `destructive`,\n * view additionally `to` when `viewTo` is given.\n */\nexport function buildDefaultActions({ flags, viewIcon, viewTo = null, handlers }) {\n\t// t() at BUILD-actions time, not module time: the labels were string\n\t// literals for the menu's whole life, which is why Edit/Copy/Delete\n\t// rendered in English in every locale despite their catalog entries\n\t// having existed all along. Runs after registerTranslations(), so the\n\t// consuming app's language is already resolved.\n\tconst out = []\n\tif (flags.view) {\n\t\tconst view = { label: t('nextcloud-vue', 'View'), icon: viewIcon, handler: handlers.onView }\n\t\tif (typeof viewTo === 'function') {\n\t\t\tview.to = viewTo\n\t\t}\n\t\tout.push(view)\n\t}\n\tif (flags.edit) {\n\t\tout.push({ label: t('nextcloud-vue', 'Edit'), icon: Pencil, handler: handlers.onEdit })\n\t}\n\tif (flags.copy) {\n\t\tout.push({ label: t('nextcloud-vue', 'Copy'), icon: ContentCopy, handler: handlers.onCopy })\n\t}\n\tif (flags.del) {\n\t\tout.push({ label: t('nextcloud-vue', 'Delete'), icon: TrashCanOutline, destructive: true, handler: handlers.onDelete })\n\t}\n\treturn out\n}\n"],"names":["t"],"mappings":";;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,mBAAmB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,EAAE;AAClF;AACA;AACA;AACA;AACA;AACA,CAAC,MAAM,GAAG,GAAG,GAAE;AACf,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE;AACjB,EAAE,MAAM,IAAI,GAAG,EAAE,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,GAAE;AAC9F,EAAE,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE;AACpC,GAAG,IAAI,CAAC,EAAE,GAAG,OAAM;AACnB,GAAG;AACH,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,EAAC;AAChB,EAAE;AACF,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE;AACjB,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAC;AACzF,EAAE;AACF,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE;AACjB,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAC;AAC9F,EAAE;AACF,CAAC,IAAI,KAAK,CAAC,GAAG,EAAE;AAChB,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,QAAQ,EAAE,EAAC;AACzH,EAAE;AACF,CAAC,OAAO,GAAG;AACX;;;;"}
1
+ {"version":3,"file":"defaultActions.js","sources":["../../../../src/components/CnIndexPage/defaultActions.js"],"sourcesContent":["import { translate as t } from '@nextcloud/l10n'\nimport ContentCopy from 'vue-material-design-icons/ContentCopy.vue'\nimport Pencil from 'vue-material-design-icons/Pencil.vue'\nimport TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'\n\n/**\n * Build CnIndexPage's built-in row actions from the show*Action flags.\n *\n * @param {object} opts The action-building options.\n * @param {{ view: boolean, edit: boolean, copy: boolean, del: boolean }} opts.flags\n * Which built-in actions to include, from the page's `show*Action` props.\n * @param {object} opts.viewIcon Component to use as the View action icon.\n * @param {((row: object) => object|null)|null} [opts.viewTo] Where View links to;\n * without it, or when it returns null, View is a button.\n * @param {{ onView: (row: object) => void, onEdit: (row: object) => void, onCopy: (row: object) => void, onDelete: (row: object) => void }} opts.handlers\n * Click handlers bound to each emitted action, in the same order as `flags`.\n * @return {Array<object>} The enabled actions in menu order (view, edit, copy, delete), each `{id, builtin: true, label, icon, handler}`.\n * The id is `view`, `edit`, `copy` or `delete`; delete additionally carries `destructive`, view additionally `to` when `viewTo` is given.\n */\nexport function buildDefaultActions({ flags, viewIcon, viewTo = null, handlers }) {\n\t// t() at BUILD-actions time, not module time: the labels were string\n\t// literals for the menu's whole life, which is why Edit/Copy/Delete\n\t// rendered in English in every locale despite their catalog entries\n\t// having existed all along. Runs after registerTranslations(), so the\n\t// consuming app's language is already resolved.\n\tconst out = []\n\tif (flags.view) {\n\t\tconst view = { id: 'view', builtin: true, label: t('nextcloud-vue', 'View'), icon: viewIcon, handler: handlers.onView }\n\t\tif (typeof viewTo === 'function') {\n\t\t\tview.to = viewTo\n\t\t}\n\t\tout.push(view)\n\t}\n\tif (flags.edit) {\n\t\tout.push({ id: 'edit', builtin: true, label: t('nextcloud-vue', 'Edit'), icon: Pencil, handler: handlers.onEdit })\n\t}\n\tif (flags.copy) {\n\t\tout.push({ id: 'copy', builtin: true, label: t('nextcloud-vue', 'Copy'), icon: ContentCopy, handler: handlers.onCopy })\n\t}\n\tif (flags.del) {\n\t\tout.push({ id: 'delete', builtin: true, label: t('nextcloud-vue', 'Delete'), icon: TrashCanOutline, destructive: true, handler: handlers.onDelete })\n\t}\n\treturn out\n}\n"],"names":["t"],"mappings":";;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,mBAAmB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,EAAE;AAClF;AACA;AACA;AACA;AACA;AACA,CAAC,MAAM,GAAG,GAAG,GAAE;AACf,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE;AACjB,EAAE,MAAM,IAAI,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,GAAE;AACzH,EAAE,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE;AACpC,GAAG,IAAI,CAAC,EAAE,GAAG,OAAM;AACnB,GAAG;AACH,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,EAAC;AAChB,EAAE;AACF,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE;AACjB,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAC;AACpH,EAAE;AACF,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE;AACjB,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAC;AACzH,EAAE;AACF,CAAC,IAAI,KAAK,CAAC,GAAG,EAAE;AAChB,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,QAAQ,EAAE,EAAC;AACtJ,EAAE;AACF,CAAC,OAAO,GAAG;AACX;;;;"}
@@ -1,7 +1,7 @@
1
1
  import { NcActions, NcActionButton, NcActionLink } from '@nextcloud/vue';
2
2
  import { resolveItemActionLink, followItemActionLink } from '../../utils/actionLink.js';
3
3
  import { isModifiedClick } from '../../utils/linkNavigation.js';
4
- import { isLocallyDecidableVisibleWhen, evaluateVisibleWhenLocal } from '../../utils/visibleWhen.js';
4
+ import { isRowActionVisible, rowActionPayload, slugifyActionLabel, rowActionTestId, rowActionKey } from '../../utils/rowActionItem.js';
5
5
  import __sfc_main from '../CnIcon/CnIcon.vue.js';
6
6
 
7
7
  /**
@@ -62,11 +62,13 @@ var script = {
62
62
  * - `to` (string | object | (row) => string | object) — render the entry as a link to this
63
63
  * vue-router location; a plain click routes in place. Ignored when the router cannot resolve it.
64
64
  * - `linkTarget` (string) — the link's `target`, e.g. `_blank`
65
+ * - `id` (string) — the action's id, passed in the `action` payload
66
+ * - `builtin` (boolean) — set by CnIndexPage on its built-in View / Edit / Copy / Delete; a built-in's testid is `cn-action-item-<id>` in every locale and its key `builtin:<id>`
65
67
  *
66
68
  * A link entry still emits `action`, but its `handler` is not called: the link is the
67
69
  * navigation. A disabled entry, or a `to` without a resolvable route, stays a button.
68
70
  *
69
- * @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}>}
71
+ * @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), title: string | ((targetItem: object) => string), destructive: boolean, href: string | ((targetItem: object) => string), to: string | object | ((targetItem: object) => string | object), linkTarget: string}>}
70
72
  */
71
73
  actions: {
72
74
  type: Array,
@@ -96,31 +98,13 @@ var script = {
96
98
 
97
99
  computed: {
98
100
  /**
99
- * Filter actions by their `visible` predicate. An action without a
100
- * `visible` field is always shown (backwards compatible).
101
+ * Filter actions by their `visibleWhen` and `visible` gates, with the same rule CnContextMenu uses.
102
+ * An action without either always shows.
101
103
  *
102
104
  * @return {Array} Visible actions for the current row.
103
105
  */
104
106
  visibleActions() {
105
- return this.actions.filter((action) => {
106
- // A manifest is JSON and cannot hold a function, so `visibleWhen` is
107
- // the only per-row gate an app configured from one can express. Only
108
- // a locally decidable condition is gated on: an endpoint/source one is
109
- // not this evaluator's question, and nothing gated a row action at all
110
- // before, so answering `false` to it would delete the entry outright.
111
- if (action.visibleWhen
112
- && isLocallyDecidableVisibleWhen(action.visibleWhen)
113
- && evaluateVisibleWhenLocal(action.visibleWhen, this.row) === false) {
114
- return false
115
- }
116
- if (action.visible === undefined) {
117
- return true
118
- }
119
- if (typeof action.visible === 'function') {
120
- return !!action.visible(this.row)
121
- }
122
- return !!action.visible
123
- })
107
+ return this.actions.filter((action) => isRowActionVisible(action, this.row))
124
108
  },
125
109
 
126
110
  /**
@@ -169,7 +153,7 @@ var script = {
169
153
  if (action.handler && typeof action.handler === 'function') {
170
154
  action.handler(this.row);
171
155
  }
172
- this.$emit('action', { action: action.label, row: this.row });
156
+ this.$emit('action', rowActionPayload(action, this.row));
173
157
  },
174
158
 
175
159
  /**
@@ -188,26 +172,41 @@ var script = {
188
172
  }
189
173
  followItemActionLink(event, link, this.$router);
190
174
  /**
191
- * @event action User picked an entry. Payload: the action's label and the row. A button entry has already run its `handler`; a link entry navigates instead, and a modified (new-tab) click on a link emits nothing.
192
- * @type {{ action: string, row: object|null }}
175
+ * @event action User picked an entry. Payload: `action` (the label), the row, the action's `id` when it has one, and `builtin: true` for a CnIndexPage built-in. A button entry has already run its `handler`; a link entry navigates instead, and a modified (new-tab) click on a link emits nothing.
176
+ * @type {{ action: string, row: object|null, id?: string, builtin?: boolean }}
193
177
  */
194
- this.$emit('action', { action: action.label, row: this.row });
178
+ this.$emit('action', rowActionPayload(action, this.row));
195
179
  },
196
180
 
197
181
  /**
198
182
  * Slugify an action label for use in stable `data-testid` selectors.
199
- * Lowercase, kebab-case, strip non-alphanumeric. Used solely by the
200
- * `:data-testid` binding on NcActionButton — does not affect runtime
201
- * behaviour or rendered text.
183
+ * Lowercase, kebab-case, strip non-alphanumeric.
202
184
  *
203
185
  * @param {string} label - The action's display label
204
186
  * @return {string} kebab-case slug suitable for a testid suffix.
205
187
  */
206
188
  slugifyLabel(label) {
207
- return String(label || '')
208
- .toLowerCase()
209
- .replace(/[^a-z0-9]+/g, '-')
210
- .replace(/^-+|-+$/g, '')
189
+ return slugifyActionLabel(label)
190
+ },
191
+
192
+ /**
193
+ * The entry's `data-testid`: `cn-action-item-<id>` for a built-in, the label slug otherwise.
194
+ *
195
+ * @param {object} action The action definition.
196
+ * @return {string} The testid.
197
+ */
198
+ actionTestId(action) {
199
+ return rowActionTestId(action)
200
+ },
201
+
202
+ /**
203
+ * The entry's render key: `builtin:<id>` for a built-in, the label otherwise.
204
+ *
205
+ * @param {object} action The action definition.
206
+ * @return {string} The key.
207
+ */
208
+ actionKey(action) {
209
+ return rowActionKey(action)
211
210
  },
212
211
  },
213
212
  };
@@ -1 +1 @@
1
- {"version":3,"file":"CnRowActions.vue2.js","sources":["../../../../src/components/CnRowActions/CnRowActions.vue"],"sourcesContent":["<template>\n\t<NcActions :forceMenu=\"visibleActions.length > 3\"\n\t\t:primary=\"primary\"\n\t\t:menuName=\"menuName\"\n\t\tdata-testid=\"cn-row-actions\">\n\t\t<template v-for=\"{ action, link } in renderedActions\" :key=\"action.label\">\n\t\t\t<!-- A navigate-only action is a real link, so it can be middle-clicked, opened in a new tab or copied. -->\n\t\t\t<NcActionLink\n\t\t\t\tv-if=\"link\"\n\t\t\t\t:href=\"link.href\"\n\t\t\t\t:target=\"link.target\"\n\t\t\t\t:title=\"getTitle(action)\"\n\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t:data-testid=\"`cn-action-item-${slugifyLabel(action.label)}`\"\n\t\t\t\tcloseAfterClick\n\t\t\t\t@click=\"onLinkAction(action, link, $event)\">\n\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ action.label }}\n\t\t\t</NcActionLink>\n\t\t\t<NcActionButton\n\t\t\t\tv-else\n\t\t\t\t:title=\"getTitle(action)\"\n\t\t\t\t:disabled=\"isDisabled(action)\"\n\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t:data-testid=\"`cn-action-item-${slugifyLabel(action.label)}`\"\n\t\t\t\tcloseAfterClick\n\t\t\t\t@click=\"onAction(action)\">\n\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ action.label }}\n\t\t\t</NcActionButton>\n\t\t</template>\n\t</NcActions>\n</template>\n\n<script>\nimport { NcActionButton, NcActionLink, NcActions } from '@nextcloud/vue'\nimport { followItemActionLink, resolveItemActionLink } from '../../utils/actionLink.js'\nimport { isModifiedClick } from '../../utils/linkNavigation.js'\nimport { evaluateVisibleWhenLocal, isLocallyDecidableVisibleWhen } from '../../utils/visibleWhen.js'\nimport { CnIcon } from '../CnIcon/index.js'\n\n/**\n * CnRowActions — Action menu wrapper for table rows and cards.\n *\n * Wraps NcActions + NcActionButton for consistent row/card action menus.\n * Actions are defined as an array of objects with label, icon, handler, etc.\n *\n * ```vue\n * <CnRowActions\n * :actions=\"[\n * { label: 'Edit', icon: PencilIcon, handler: (row) => editRow(row) },\n * { label: 'Delete', icon: TrashIcon, handler: (row) => deleteRow(row), destructive: true },\n * ]\"\n * :row=\"row\" />\n * ```\n *\n * An action whose only job is to go somewhere carries `href` (a URL) or `to`\n * (a vue-router location) instead of a navigating `handler`, and renders as a\n * real link — middle-click, \"open in new tab\" and \"copy link\" all work:\n *\n * ```vue\n * <CnRowActions\n * :actions=\"[\n * { label: 'View', icon: 'Eye', to: (row) => ({ name: 'LeadDetail', params: { id: row.id } }) },\n * { label: 'Website', icon: 'Web', href: (row) => row.url, linkTarget: '_blank' },\n * ]\"\n * :row=\"row\" />\n * ```\n */\nexport default {\n\tname: 'CnRowActions',\n\n\tcomponents: {\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tCnIcon,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Action definitions.\n\t\t *\n\t\t * Each action supports:\n\t\t * - `label` (string, required) — display text\n\t\t * - `icon` (component | string) — MDI icon. A component renders\n\t\t * directly; a string is treated as a registry name and rendered\n\t\t * via `CnIcon` (PascalCase, e.g. `\"Eye\"`), falling back to the\n\t\t * help-circle when unregistered. The string form lets manifest\n\t\t * (JSON) actions declare icons by name.\n\t\t * - `handler` (function) — called with `row` on click\n\t\t * - `disabled` (boolean | (row) => boolean) — gray out the entry\n\t\t * - `visible` (boolean | (row) => boolean) — when `false`, hide the entry from the menu (default: shown)\n\t\t * - `title` (string | (row) => string) — native tooltip shown on hover (useful to explain why an entry is disabled)\n\t\t * - `destructive` (boolean) — apply error color styling\n\t\t * - `href` (string | (row) => string) — render the entry as a link to this URL\n\t\t * - `to` (string | object | (row) => string | object) — render the entry as a link to this\n\t\t * vue-router location; a plain click routes in place. Ignored when the router cannot resolve it.\n\t\t * - `linkTarget` (string) — the link's `target`, e.g. `_blank`\n\t\t *\n\t\t * A link entry still emits `action`, but its `handler` is not called: the link is the\n\t\t * navigation. A disabled entry, or a `to` without a resolvable route, stays a button.\n\t\t *\n\t\t * @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}>}\n\t\t */\n\t\tactions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** The row/object data (passed to action handlers) */\n\t\trow: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether to use primary styling for the action menu trigger */\n\t\tprimary: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Label shown on the action menu trigger button */\n\t\tmenuName: {\n\t\t\ttype: String,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\n\temits: ['action'],\n\n\tcomputed: {\n\t\t/**\n\t\t * Filter actions by their `visible` predicate. An action without a\n\t\t * `visible` field is always shown (backwards compatible).\n\t\t *\n\t\t * @return {Array} Visible actions for the current row.\n\t\t */\n\t\tvisibleActions() {\n\t\t\treturn this.actions.filter((action) => {\n\t\t\t\t// A manifest is JSON and cannot hold a function, so `visibleWhen` is\n\t\t\t\t// the only per-row gate an app configured from one can express. Only\n\t\t\t\t// a locally decidable condition is gated on: an endpoint/source one is\n\t\t\t\t// not this evaluator's question, and nothing gated a row action at all\n\t\t\t\t// before, so answering `false` to it would delete the entry outright.\n\t\t\t\tif (action.visibleWhen\n\t\t\t\t\t&& isLocallyDecidableVisibleWhen(action.visibleWhen)\n\t\t\t\t\t&& evaluateVisibleWhenLocal(action.visibleWhen, this.row) === false) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tif (action.visible === undefined) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tif (typeof action.visible === 'function') {\n\t\t\t\t\treturn !!action.visible(this.row)\n\t\t\t\t}\n\t\t\t\treturn !!action.visible\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Visible actions paired with the link each renders as (null for a button).\n\t\t *\n\t\t * @return {Array<{action: object, link: object|null}>}\n\t\t */\n\t\trenderedActions() {\n\t\t\treturn this.visibleActions.map((action) => ({\n\t\t\t\taction,\n\t\t\t\tlink: this.isDisabled(action) ? null : resolveItemActionLink(action, this.row, this.$router),\n\t\t\t}))\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve disabled state for an action — supports both boolean and function.\n\t\t *\n\t\t * @param {object} action - The action definition\n\t\t * @return {boolean} Whether the action is disabled\n\t\t */\n\t\tisDisabled(action) {\n\t\t\tif (typeof action.disabled === 'function') {\n\t\t\t\treturn action.disabled(this.row)\n\t\t\t}\n\t\t\treturn !!action.disabled\n\t\t},\n\n\t\t/**\n\t\t * Resolve the title (native tooltip) for an action — supports both\n\t\t * string and function forms. Returns undefined when no title is\n\t\t * provided so the attribute is not rendered.\n\t\t *\n\t\t * @param {object} action - The action definition\n\t\t * @return {string|undefined} The resolved tooltip text, or undefined.\n\t\t */\n\t\tgetTitle(action) {\n\t\t\tif (typeof action.title === 'function') {\n\t\t\t\treturn action.title(this.row) || undefined\n\t\t\t}\n\t\t\treturn action.title || undefined\n\t\t},\n\n\t\tonAction(action) {\n\t\t\tif (action.handler && typeof action.handler === 'function') {\n\t\t\t\taction.handler(this.row)\n\t\t\t}\n\t\t\tthis.$emit('action', { action: action.label, row: this.row })\n\t\t},\n\n\t\t/**\n\t\t * A link entry was clicked: route a plain in-app click, leave the rest to\n\t\t * the browser, and emit `action` as a button would. The `handler` is not\n\t\t * called, since navigating is what the link already does. A modified\n\t\t * click opens a new tab, so nothing is emitted in this one.\n\t\t *\n\t\t * @param {object} action The action definition.\n\t\t * @param {object} link The resolved link.\n\t\t * @param {MouseEvent} event The click event.\n\t\t */\n\t\tonLinkAction(action, link, event) {\n\t\t\tif (isModifiedClick(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfollowItemActionLink(event, link, this.$router)\n\t\t\t/**\n\t\t\t * @event action User picked an entry. Payload: the action's label and the row. A button entry has already run its `handler`; a link entry navigates instead, and a modified (new-tab) click on a link emits nothing.\n\t\t\t * @type {{ action: string, row: object|null }}\n\t\t\t */\n\t\t\tthis.$emit('action', { action: action.label, row: this.row })\n\t\t},\n\n\t\t/**\n\t\t * Slugify an action label for use in stable `data-testid` selectors.\n\t\t * Lowercase, kebab-case, strip non-alphanumeric. Used solely by the\n\t\t * `:data-testid` binding on NcActionButton — does not affect runtime\n\t\t * behaviour or rendered text.\n\t\t *\n\t\t * @param {string} label - The action's display label\n\t\t * @return {string} kebab-case slug suitable for a testid suffix.\n\t\t */\n\t\tslugifyLabel(label) {\n\t\t\treturn String(label || '')\n\t\t\t\t.toLowerCase()\n\t\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t\t.replace(/^-+|-+$/g, '')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-row-action--destructive {\n\tcolor: var(--color-error) !important;\n}\n</style>\n"],"names":["CnIcon"],"mappings":"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"}
1
+ {"version":3,"file":"CnRowActions.vue2.js","sources":["../../../../src/components/CnRowActions/CnRowActions.vue"],"sourcesContent":["<template>\n\t<NcActions :forceMenu=\"visibleActions.length > 3\"\n\t\t:primary=\"primary\"\n\t\t:menuName=\"menuName\"\n\t\tdata-testid=\"cn-row-actions\">\n\t\t<template v-for=\"{ action, link } in renderedActions\" :key=\"actionKey(action)\">\n\t\t\t<!-- A navigate-only action is a real link, so it can be middle-clicked, opened in a new tab or copied. -->\n\t\t\t<NcActionLink\n\t\t\t\tv-if=\"link\"\n\t\t\t\t:href=\"link.href\"\n\t\t\t\t:target=\"link.target\"\n\t\t\t\t:title=\"getTitle(action)\"\n\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t:data-testid=\"actionTestId(action)\"\n\t\t\t\tcloseAfterClick\n\t\t\t\t@click=\"onLinkAction(action, link, $event)\">\n\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ action.label }}\n\t\t\t</NcActionLink>\n\t\t\t<NcActionButton\n\t\t\t\tv-else\n\t\t\t\t:title=\"getTitle(action)\"\n\t\t\t\t:disabled=\"isDisabled(action)\"\n\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t:data-testid=\"actionTestId(action)\"\n\t\t\t\tcloseAfterClick\n\t\t\t\t@click=\"onAction(action)\">\n\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ action.label }}\n\t\t\t</NcActionButton>\n\t\t</template>\n\t</NcActions>\n</template>\n\n<script>\nimport { NcActionButton, NcActionLink, NcActions } from '@nextcloud/vue'\nimport { followItemActionLink, resolveItemActionLink } from '../../utils/actionLink.js'\nimport { isModifiedClick } from '../../utils/linkNavigation.js'\nimport { isRowActionVisible, rowActionKey, rowActionPayload, rowActionTestId, slugifyActionLabel } from '../../utils/rowActionItem.js'\nimport { CnIcon } from '../CnIcon/index.js'\n\n/**\n * CnRowActions — Action menu wrapper for table rows and cards.\n *\n * Wraps NcActions + NcActionButton for consistent row/card action menus.\n * Actions are defined as an array of objects with label, icon, handler, etc.\n *\n * ```vue\n * <CnRowActions\n * :actions=\"[\n * { label: 'Edit', icon: PencilIcon, handler: (row) => editRow(row) },\n * { label: 'Delete', icon: TrashIcon, handler: (row) => deleteRow(row), destructive: true },\n * ]\"\n * :row=\"row\" />\n * ```\n *\n * An action whose only job is to go somewhere carries `href` (a URL) or `to`\n * (a vue-router location) instead of a navigating `handler`, and renders as a\n * real link — middle-click, \"open in new tab\" and \"copy link\" all work:\n *\n * ```vue\n * <CnRowActions\n * :actions=\"[\n * { label: 'View', icon: 'Eye', to: (row) => ({ name: 'LeadDetail', params: { id: row.id } }) },\n * { label: 'Website', icon: 'Web', href: (row) => row.url, linkTarget: '_blank' },\n * ]\"\n * :row=\"row\" />\n * ```\n */\nexport default {\n\tname: 'CnRowActions',\n\n\tcomponents: {\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tCnIcon,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Action definitions.\n\t\t *\n\t\t * Each action supports:\n\t\t * - `label` (string, required) — display text\n\t\t * - `icon` (component | string) — MDI icon. A component renders\n\t\t * directly; a string is treated as a registry name and rendered\n\t\t * via `CnIcon` (PascalCase, e.g. `\"Eye\"`), falling back to the\n\t\t * help-circle when unregistered. The string form lets manifest\n\t\t * (JSON) actions declare icons by name.\n\t\t * - `handler` (function) — called with `row` on click\n\t\t * - `disabled` (boolean | (row) => boolean) — gray out the entry\n\t\t * - `visible` (boolean | (row) => boolean) — when `false`, hide the entry from the menu (default: shown)\n\t\t * - `title` (string | (row) => string) — native tooltip shown on hover (useful to explain why an entry is disabled)\n\t\t * - `destructive` (boolean) — apply error color styling\n\t\t * - `href` (string | (row) => string) — render the entry as a link to this URL\n\t\t * - `to` (string | object | (row) => string | object) — render the entry as a link to this\n\t\t * vue-router location; a plain click routes in place. Ignored when the router cannot resolve it.\n\t\t * - `linkTarget` (string) — the link's `target`, e.g. `_blank`\n\t\t * - `id` (string) — the action's id, passed in the `action` payload\n\t\t * - `builtin` (boolean) — set by CnIndexPage on its built-in View / Edit / Copy / Delete; a built-in's testid is `cn-action-item-<id>` in every locale and its key `builtin:<id>`\n\t\t *\n\t\t * A link entry still emits `action`, but its `handler` is not called: the link is the\n\t\t * navigation. A disabled entry, or a `to` without a resolvable route, stays a button.\n\t\t *\n\t\t * @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), title: string | ((targetItem: object) => string), destructive: boolean, href: string | ((targetItem: object) => string), to: string | object | ((targetItem: object) => string | object), linkTarget: string}>}\n\t\t */\n\t\tactions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** The row/object data (passed to action handlers) */\n\t\trow: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether to use primary styling for the action menu trigger */\n\t\tprimary: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Label shown on the action menu trigger button */\n\t\tmenuName: {\n\t\t\ttype: String,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\n\temits: ['action'],\n\n\tcomputed: {\n\t\t/**\n\t\t * Filter actions by their `visibleWhen` and `visible` gates, with the same rule CnContextMenu uses.\n\t\t * An action without either always shows.\n\t\t *\n\t\t * @return {Array} Visible actions for the current row.\n\t\t */\n\t\tvisibleActions() {\n\t\t\treturn this.actions.filter((action) => isRowActionVisible(action, this.row))\n\t\t},\n\n\t\t/**\n\t\t * Visible actions paired with the link each renders as (null for a button).\n\t\t *\n\t\t * @return {Array<{action: object, link: object|null}>}\n\t\t */\n\t\trenderedActions() {\n\t\t\treturn this.visibleActions.map((action) => ({\n\t\t\t\taction,\n\t\t\t\tlink: this.isDisabled(action) ? null : resolveItemActionLink(action, this.row, this.$router),\n\t\t\t}))\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve disabled state for an action — supports both boolean and function.\n\t\t *\n\t\t * @param {object} action - The action definition\n\t\t * @return {boolean} Whether the action is disabled\n\t\t */\n\t\tisDisabled(action) {\n\t\t\tif (typeof action.disabled === 'function') {\n\t\t\t\treturn action.disabled(this.row)\n\t\t\t}\n\t\t\treturn !!action.disabled\n\t\t},\n\n\t\t/**\n\t\t * Resolve the title (native tooltip) for an action — supports both\n\t\t * string and function forms. Returns undefined when no title is\n\t\t * provided so the attribute is not rendered.\n\t\t *\n\t\t * @param {object} action - The action definition\n\t\t * @return {string|undefined} The resolved tooltip text, or undefined.\n\t\t */\n\t\tgetTitle(action) {\n\t\t\tif (typeof action.title === 'function') {\n\t\t\t\treturn action.title(this.row) || undefined\n\t\t\t}\n\t\t\treturn action.title || undefined\n\t\t},\n\n\t\tonAction(action) {\n\t\t\tif (action.handler && typeof action.handler === 'function') {\n\t\t\t\taction.handler(this.row)\n\t\t\t}\n\t\t\tthis.$emit('action', rowActionPayload(action, this.row))\n\t\t},\n\n\t\t/**\n\t\t * A link entry was clicked: route a plain in-app click, leave the rest to\n\t\t * the browser, and emit `action` as a button would. The `handler` is not\n\t\t * called, since navigating is what the link already does. A modified\n\t\t * click opens a new tab, so nothing is emitted in this one.\n\t\t *\n\t\t * @param {object} action The action definition.\n\t\t * @param {object} link The resolved link.\n\t\t * @param {MouseEvent} event The click event.\n\t\t */\n\t\tonLinkAction(action, link, event) {\n\t\t\tif (isModifiedClick(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfollowItemActionLink(event, link, this.$router)\n\t\t\t/**\n\t\t\t * @event action User picked an entry. Payload: `action` (the label), the row, the action's `id` when it has one, and `builtin: true` for a CnIndexPage built-in. A button entry has already run its `handler`; a link entry navigates instead, and a modified (new-tab) click on a link emits nothing.\n\t\t\t * @type {{ action: string, row: object|null, id?: string, builtin?: boolean }}\n\t\t\t */\n\t\t\tthis.$emit('action', rowActionPayload(action, this.row))\n\t\t},\n\n\t\t/**\n\t\t * Slugify an action label for use in stable `data-testid` selectors.\n\t\t * Lowercase, kebab-case, strip non-alphanumeric.\n\t\t *\n\t\t * @param {string} label - The action's display label\n\t\t * @return {string} kebab-case slug suitable for a testid suffix.\n\t\t */\n\t\tslugifyLabel(label) {\n\t\t\treturn slugifyActionLabel(label)\n\t\t},\n\n\t\t/**\n\t\t * The entry's `data-testid`: `cn-action-item-<id>` for a built-in, the label slug otherwise.\n\t\t *\n\t\t * @param {object} action The action definition.\n\t\t * @return {string} The testid.\n\t\t */\n\t\tactionTestId(action) {\n\t\t\treturn rowActionTestId(action)\n\t\t},\n\n\t\t/**\n\t\t * The entry's render key: `builtin:<id>` for a built-in, the label otherwise.\n\t\t *\n\t\t * @param {object} action The action definition.\n\t\t * @return {string} The key.\n\t\t */\n\t\tactionKey(action) {\n\t\t\treturn rowActionKey(action)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-row-action--destructive {\n\tcolor: var(--color-error) !important;\n}\n</style>\n"],"names":["CnIcon"],"mappings":"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"}
@@ -15,7 +15,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
15
15
  default: withCtx(() => [
16
16
  (openBlock(true), createElementBlock(Fragment, null, renderList($options.renderedActions, ({ action, link }) => {
17
17
  return (openBlock(), createElementBlock(Fragment, {
18
- key: action.label
18
+ key: $options.actionKey(action)
19
19
  }, [
20
20
  link
21
21
  ? (openBlock(), createBlock(_component_NcActionLink, {
@@ -24,7 +24,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
24
24
  target: link.target,
25
25
  title: $options.getTitle(action),
26
26
  class: normalizeClass({ 'cn-row-action--destructive': action.destructive }),
27
- "data-testid": `cn-action-item-${$options.slugifyLabel(action.label)}`,
27
+ "data-testid": $options.actionTestId(action),
28
28
  closeAfterClick: "",
29
29
  onClick: $event => ($options.onLinkAction(action, link, $event))
30
30
  }, createSlots({
@@ -57,7 +57,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
57
57
  title: $options.getTitle(action),
58
58
  disabled: $options.isDisabled(action),
59
59
  class: normalizeClass({ 'cn-row-action--destructive': action.destructive }),
60
- "data-testid": `cn-action-item-${$options.slugifyLabel(action.label)}`,
60
+ "data-testid": $options.actionTestId(action),
61
61
  closeAfterClick: "",
62
62
  onClick: $event => ($options.onAction(action))
63
63
  }, createSlots({
@@ -1 +1 @@
1
- {"version":3,"file":"CnRowActions.vue3.js","sources":["../../../../src/components/CnRowActions/CnRowActions.vue?vue&type=template&id=5d9db5e2&lang.js"],"sourcesContent":["<template>\n\t<NcActions :forceMenu=\"visibleActions.length > 3\"\n\t\t:primary=\"primary\"\n\t\t:menuName=\"menuName\"\n\t\tdata-testid=\"cn-row-actions\">\n\t\t<template v-for=\"{ action, link } in renderedActions\" :key=\"action.label\">\n\t\t\t<!-- A navigate-only action is a real link, so it can be middle-clicked, opened in a new tab or copied. -->\n\t\t\t<NcActionLink\n\t\t\t\tv-if=\"link\"\n\t\t\t\t:href=\"link.href\"\n\t\t\t\t:target=\"link.target\"\n\t\t\t\t:title=\"getTitle(action)\"\n\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t:data-testid=\"`cn-action-item-${slugifyLabel(action.label)}`\"\n\t\t\t\tcloseAfterClick\n\t\t\t\t@click=\"onLinkAction(action, link, $event)\">\n\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ action.label }}\n\t\t\t</NcActionLink>\n\t\t\t<NcActionButton\n\t\t\t\tv-else\n\t\t\t\t:title=\"getTitle(action)\"\n\t\t\t\t:disabled=\"isDisabled(action)\"\n\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t:data-testid=\"`cn-action-item-${slugifyLabel(action.label)}`\"\n\t\t\t\tcloseAfterClick\n\t\t\t\t@click=\"onAction(action)\">\n\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ action.label }}\n\t\t\t</NcActionButton>\n\t\t</template>\n\t</NcActions>\n</template>\n\n<script>\nimport { NcActionButton, NcActionLink, NcActions } from '@nextcloud/vue'\nimport { followItemActionLink, resolveItemActionLink } from '../../utils/actionLink.js'\nimport { isModifiedClick } from '../../utils/linkNavigation.js'\nimport { evaluateVisibleWhenLocal, isLocallyDecidableVisibleWhen } from '../../utils/visibleWhen.js'\nimport { CnIcon } from '../CnIcon/index.js'\n\n/**\n * CnRowActions — Action menu wrapper for table rows and cards.\n *\n * Wraps NcActions + NcActionButton for consistent row/card action menus.\n * Actions are defined as an array of objects with label, icon, handler, etc.\n *\n * ```vue\n * <CnRowActions\n * :actions=\"[\n * { label: 'Edit', icon: PencilIcon, handler: (row) => editRow(row) },\n * { label: 'Delete', icon: TrashIcon, handler: (row) => deleteRow(row), destructive: true },\n * ]\"\n * :row=\"row\" />\n * ```\n *\n * An action whose only job is to go somewhere carries `href` (a URL) or `to`\n * (a vue-router location) instead of a navigating `handler`, and renders as a\n * real link — middle-click, \"open in new tab\" and \"copy link\" all work:\n *\n * ```vue\n * <CnRowActions\n * :actions=\"[\n * { label: 'View', icon: 'Eye', to: (row) => ({ name: 'LeadDetail', params: { id: row.id } }) },\n * { label: 'Website', icon: 'Web', href: (row) => row.url, linkTarget: '_blank' },\n * ]\"\n * :row=\"row\" />\n * ```\n */\nexport default {\n\tname: 'CnRowActions',\n\n\tcomponents: {\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tCnIcon,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Action definitions.\n\t\t *\n\t\t * Each action supports:\n\t\t * - `label` (string, required) — display text\n\t\t * - `icon` (component | string) — MDI icon. A component renders\n\t\t * directly; a string is treated as a registry name and rendered\n\t\t * via `CnIcon` (PascalCase, e.g. `\"Eye\"`), falling back to the\n\t\t * help-circle when unregistered. The string form lets manifest\n\t\t * (JSON) actions declare icons by name.\n\t\t * - `handler` (function) — called with `row` on click\n\t\t * - `disabled` (boolean | (row) => boolean) — gray out the entry\n\t\t * - `visible` (boolean | (row) => boolean) — when `false`, hide the entry from the menu (default: shown)\n\t\t * - `title` (string | (row) => string) — native tooltip shown on hover (useful to explain why an entry is disabled)\n\t\t * - `destructive` (boolean) — apply error color styling\n\t\t * - `href` (string | (row) => string) — render the entry as a link to this URL\n\t\t * - `to` (string | object | (row) => string | object) — render the entry as a link to this\n\t\t * vue-router location; a plain click routes in place. Ignored when the router cannot resolve it.\n\t\t * - `linkTarget` (string) — the link's `target`, e.g. `_blank`\n\t\t *\n\t\t * A link entry still emits `action`, but its `handler` is not called: the link is the\n\t\t * navigation. A disabled entry, or a `to` without a resolvable route, stays a button.\n\t\t *\n\t\t * @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}>}\n\t\t */\n\t\tactions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** The row/object data (passed to action handlers) */\n\t\trow: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether to use primary styling for the action menu trigger */\n\t\tprimary: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Label shown on the action menu trigger button */\n\t\tmenuName: {\n\t\t\ttype: String,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\n\temits: ['action'],\n\n\tcomputed: {\n\t\t/**\n\t\t * Filter actions by their `visible` predicate. An action without a\n\t\t * `visible` field is always shown (backwards compatible).\n\t\t *\n\t\t * @return {Array} Visible actions for the current row.\n\t\t */\n\t\tvisibleActions() {\n\t\t\treturn this.actions.filter((action) => {\n\t\t\t\t// A manifest is JSON and cannot hold a function, so `visibleWhen` is\n\t\t\t\t// the only per-row gate an app configured from one can express. Only\n\t\t\t\t// a locally decidable condition is gated on: an endpoint/source one is\n\t\t\t\t// not this evaluator's question, and nothing gated a row action at all\n\t\t\t\t// before, so answering `false` to it would delete the entry outright.\n\t\t\t\tif (action.visibleWhen\n\t\t\t\t\t&& isLocallyDecidableVisibleWhen(action.visibleWhen)\n\t\t\t\t\t&& evaluateVisibleWhenLocal(action.visibleWhen, this.row) === false) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tif (action.visible === undefined) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tif (typeof action.visible === 'function') {\n\t\t\t\t\treturn !!action.visible(this.row)\n\t\t\t\t}\n\t\t\t\treturn !!action.visible\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Visible actions paired with the link each renders as (null for a button).\n\t\t *\n\t\t * @return {Array<{action: object, link: object|null}>}\n\t\t */\n\t\trenderedActions() {\n\t\t\treturn this.visibleActions.map((action) => ({\n\t\t\t\taction,\n\t\t\t\tlink: this.isDisabled(action) ? null : resolveItemActionLink(action, this.row, this.$router),\n\t\t\t}))\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve disabled state for an action — supports both boolean and function.\n\t\t *\n\t\t * @param {object} action - The action definition\n\t\t * @return {boolean} Whether the action is disabled\n\t\t */\n\t\tisDisabled(action) {\n\t\t\tif (typeof action.disabled === 'function') {\n\t\t\t\treturn action.disabled(this.row)\n\t\t\t}\n\t\t\treturn !!action.disabled\n\t\t},\n\n\t\t/**\n\t\t * Resolve the title (native tooltip) for an action — supports both\n\t\t * string and function forms. Returns undefined when no title is\n\t\t * provided so the attribute is not rendered.\n\t\t *\n\t\t * @param {object} action - The action definition\n\t\t * @return {string|undefined} The resolved tooltip text, or undefined.\n\t\t */\n\t\tgetTitle(action) {\n\t\t\tif (typeof action.title === 'function') {\n\t\t\t\treturn action.title(this.row) || undefined\n\t\t\t}\n\t\t\treturn action.title || undefined\n\t\t},\n\n\t\tonAction(action) {\n\t\t\tif (action.handler && typeof action.handler === 'function') {\n\t\t\t\taction.handler(this.row)\n\t\t\t}\n\t\t\tthis.$emit('action', { action: action.label, row: this.row })\n\t\t},\n\n\t\t/**\n\t\t * A link entry was clicked: route a plain in-app click, leave the rest to\n\t\t * the browser, and emit `action` as a button would. The `handler` is not\n\t\t * called, since navigating is what the link already does. A modified\n\t\t * click opens a new tab, so nothing is emitted in this one.\n\t\t *\n\t\t * @param {object} action The action definition.\n\t\t * @param {object} link The resolved link.\n\t\t * @param {MouseEvent} event The click event.\n\t\t */\n\t\tonLinkAction(action, link, event) {\n\t\t\tif (isModifiedClick(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfollowItemActionLink(event, link, this.$router)\n\t\t\t/**\n\t\t\t * @event action User picked an entry. Payload: the action's label and the row. A button entry has already run its `handler`; a link entry navigates instead, and a modified (new-tab) click on a link emits nothing.\n\t\t\t * @type {{ action: string, row: object|null }}\n\t\t\t */\n\t\t\tthis.$emit('action', { action: action.label, row: this.row })\n\t\t},\n\n\t\t/**\n\t\t * Slugify an action label for use in stable `data-testid` selectors.\n\t\t * Lowercase, kebab-case, strip non-alphanumeric. Used solely by the\n\t\t * `:data-testid` binding on NcActionButton — does not affect runtime\n\t\t * behaviour or rendered text.\n\t\t *\n\t\t * @param {string} label - The action's display label\n\t\t * @return {string} kebab-case slug suitable for a testid suffix.\n\t\t */\n\t\tslugifyLabel(label) {\n\t\t\treturn String(label || '')\n\t\t\t\t.toLowerCase()\n\t\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t\t.replace(/^-+|-+$/g, '')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-row-action--destructive {\n\tcolor: var(--color-error) !important;\n}\n</style>\n"],"names":["_createBlock","_openBlock","_createElementBlock","_Fragment","_renderList","_normalizeClass","_toDisplayString","_resolveDynamicComponent"],"mappings":";;;;;;;;uBACCA,WAoCW,CAAA,oBAAA,EAAA;IApCC,SAAS,EAAE,QAAc,CAAA,cAAA,CAAC,MAAK,GAAA,CAAA;AACzC,IAAA,OAAO,EAAE,MAAO,CAAA,OAAA;AAChB,IAAA,QAAQ,EAAE,MAAQ,CAAA,QAAA;AACnB,IAAA,aAAW,EAAC,gBAAgB;;qBAClB,MAA0C;AAApD,OAAAC,SAAA,CAAA,IAAA,CAAA,EAAAC,kBAAA,CA+BUC,QA/B2B,EAAA,IAAA,EAAAC,UAAA,CAAA,QAAA,CAAA,eAAe,EAAjC,CAAA,EAAA,MAAM,EAAE,IAAG,EAAA,KAAA;;AAA8B,UAAA,GAAA,EAAA,MAAM,CAAC,KAAK;;UAGhE,IAAI;4BADXJ,WAcc,CAAA,uBAAA,EAAA;;gBAZZ,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,KAAK,EAAE,QAAQ,CAAA,QAAA,CAAC,MAAM,CAAA;gBACtB,KAAK,EAAAK,cAAA,CAAA,EAAA,4BAAA,EAAkC,MAAM,CAAC,WAAU,EAAA,CAAA;AACxD,gBAAA,aAAW,EAAoB,CAAA,eAAA,EAAA,QAAA,CAAA,YAAY,CAAC,MAAM,CAAC,KAAK,CAAA,CAAA,CAAA;AACzD,gBAAA,eAAc,EAAd,EAAc;AACb,gBAAA,OAAK,aAAE,QAAY,CAAA,YAAA,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAA,CAAA;;iCAI/B,MACV;kCADU,GACV,GAAAC,eAAA,CAAG,MAAM,CAAC;;;;AAJM,gBAAA,CAAA,MAAM,CAAC,IAAI;;4BAAG,MAAI;kCACjC,MAA+E;AAA1D,wBAAA,CAAA,OAAA,MAAM,CAAC,IAAK,KAAA,QAAA;0CAAjCN,WAA+E,CAAA,iBAAA,EAAA;;8BAA/B,IAAI,EAAE,MAAM,CAAC,IAAI;AAAG,8BAAA,IAAI,EAAE,EAAE;;0CAC5EA,WAAgD,CAAAO,uBAAA,CAAhC,MAAM,CAAC,IAAI,CAAA,EAAA;;AAAU,8BAAA,IAAI,EAAE,EAAE;;;;;;;4BAI/CP,WAagB,CAAA,yBAAA,EAAA;;gBAXd,KAAK,EAAE,QAAQ,CAAA,QAAA,CAAC,MAAM,CAAA;gBACtB,QAAQ,EAAE,QAAU,CAAA,UAAA,CAAC,MAAM,CAAA;gBAC3B,KAAK,EAAAK,cAAA,CAAA,EAAA,4BAAA,EAAkC,MAAM,CAAC,WAAU,EAAA,CAAA;AACxD,gBAAA,aAAW,EAAoB,CAAA,eAAA,EAAA,QAAA,CAAA,YAAY,CAAC,MAAM,CAAC,KAAK,CAAA,CAAA,CAAA;AACzD,gBAAA,eAAc,EAAd,EAAc;gBACb,OAAK,EAAA,MAAA,KAAE,QAAQ,CAAA,QAAA,CAAC,MAAM,CAAA,CAAA;;iCAIb,MACV;kCADU,GACV,GAAAC,eAAA,CAAG,MAAM,CAAC;;;;AAJM,gBAAA,CAAA,MAAM,CAAC,IAAI;;4BAAG,MAAI;kCACjC,MAA+E;AAA1D,wBAAA,CAAA,OAAA,MAAM,CAAC,IAAK,KAAA,QAAA;0CAAjCN,WAA+E,CAAA,iBAAA,EAAA;;8BAA/B,IAAI,EAAE,MAAM,CAAC,IAAI;AAAG,8BAAA,IAAI,EAAE,EAAE;;0CAC5EA,WAAgD,CAAAO,uBAAA,CAAhC,MAAM,CAAC,IAAI,CAAA,EAAA;;AAAU,8BAAA,IAAI,EAAE,EAAE;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnRowActions.vue3.js","sources":["../../../../src/components/CnRowActions/CnRowActions.vue?vue&type=template&id=5d9db5e2&lang.js"],"sourcesContent":["<template>\n\t<NcActions :forceMenu=\"visibleActions.length > 3\"\n\t\t:primary=\"primary\"\n\t\t:menuName=\"menuName\"\n\t\tdata-testid=\"cn-row-actions\">\n\t\t<template v-for=\"{ action, link } in renderedActions\" :key=\"actionKey(action)\">\n\t\t\t<!-- A navigate-only action is a real link, so it can be middle-clicked, opened in a new tab or copied. -->\n\t\t\t<NcActionLink\n\t\t\t\tv-if=\"link\"\n\t\t\t\t:href=\"link.href\"\n\t\t\t\t:target=\"link.target\"\n\t\t\t\t:title=\"getTitle(action)\"\n\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t:data-testid=\"actionTestId(action)\"\n\t\t\t\tcloseAfterClick\n\t\t\t\t@click=\"onLinkAction(action, link, $event)\">\n\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ action.label }}\n\t\t\t</NcActionLink>\n\t\t\t<NcActionButton\n\t\t\t\tv-else\n\t\t\t\t:title=\"getTitle(action)\"\n\t\t\t\t:disabled=\"isDisabled(action)\"\n\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t:data-testid=\"actionTestId(action)\"\n\t\t\t\tcloseAfterClick\n\t\t\t\t@click=\"onAction(action)\">\n\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ action.label }}\n\t\t\t</NcActionButton>\n\t\t</template>\n\t</NcActions>\n</template>\n\n<script>\nimport { NcActionButton, NcActionLink, NcActions } from '@nextcloud/vue'\nimport { followItemActionLink, resolveItemActionLink } from '../../utils/actionLink.js'\nimport { isModifiedClick } from '../../utils/linkNavigation.js'\nimport { isRowActionVisible, rowActionKey, rowActionPayload, rowActionTestId, slugifyActionLabel } from '../../utils/rowActionItem.js'\nimport { CnIcon } from '../CnIcon/index.js'\n\n/**\n * CnRowActions — Action menu wrapper for table rows and cards.\n *\n * Wraps NcActions + NcActionButton for consistent row/card action menus.\n * Actions are defined as an array of objects with label, icon, handler, etc.\n *\n * ```vue\n * <CnRowActions\n * :actions=\"[\n * { label: 'Edit', icon: PencilIcon, handler: (row) => editRow(row) },\n * { label: 'Delete', icon: TrashIcon, handler: (row) => deleteRow(row), destructive: true },\n * ]\"\n * :row=\"row\" />\n * ```\n *\n * An action whose only job is to go somewhere carries `href` (a URL) or `to`\n * (a vue-router location) instead of a navigating `handler`, and renders as a\n * real link — middle-click, \"open in new tab\" and \"copy link\" all work:\n *\n * ```vue\n * <CnRowActions\n * :actions=\"[\n * { label: 'View', icon: 'Eye', to: (row) => ({ name: 'LeadDetail', params: { id: row.id } }) },\n * { label: 'Website', icon: 'Web', href: (row) => row.url, linkTarget: '_blank' },\n * ]\"\n * :row=\"row\" />\n * ```\n */\nexport default {\n\tname: 'CnRowActions',\n\n\tcomponents: {\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tCnIcon,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Action definitions.\n\t\t *\n\t\t * Each action supports:\n\t\t * - `label` (string, required) — display text\n\t\t * - `icon` (component | string) — MDI icon. A component renders\n\t\t * directly; a string is treated as a registry name and rendered\n\t\t * via `CnIcon` (PascalCase, e.g. `\"Eye\"`), falling back to the\n\t\t * help-circle when unregistered. The string form lets manifest\n\t\t * (JSON) actions declare icons by name.\n\t\t * - `handler` (function) — called with `row` on click\n\t\t * - `disabled` (boolean | (row) => boolean) — gray out the entry\n\t\t * - `visible` (boolean | (row) => boolean) — when `false`, hide the entry from the menu (default: shown)\n\t\t * - `title` (string | (row) => string) — native tooltip shown on hover (useful to explain why an entry is disabled)\n\t\t * - `destructive` (boolean) — apply error color styling\n\t\t * - `href` (string | (row) => string) — render the entry as a link to this URL\n\t\t * - `to` (string | object | (row) => string | object) — render the entry as a link to this\n\t\t * vue-router location; a plain click routes in place. Ignored when the router cannot resolve it.\n\t\t * - `linkTarget` (string) — the link's `target`, e.g. `_blank`\n\t\t * - `id` (string) — the action's id, passed in the `action` payload\n\t\t * - `builtin` (boolean) — set by CnIndexPage on its built-in View / Edit / Copy / Delete; a built-in's testid is `cn-action-item-<id>` in every locale and its key `builtin:<id>`\n\t\t *\n\t\t * A link entry still emits `action`, but its `handler` is not called: the link is the\n\t\t * navigation. A disabled entry, or a `to` without a resolvable route, stays a button.\n\t\t *\n\t\t * @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), title: string | ((targetItem: object) => string), destructive: boolean, href: string | ((targetItem: object) => string), to: string | object | ((targetItem: object) => string | object), linkTarget: string}>}\n\t\t */\n\t\tactions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** The row/object data (passed to action handlers) */\n\t\trow: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether to use primary styling for the action menu trigger */\n\t\tprimary: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Label shown on the action menu trigger button */\n\t\tmenuName: {\n\t\t\ttype: String,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\n\temits: ['action'],\n\n\tcomputed: {\n\t\t/**\n\t\t * Filter actions by their `visibleWhen` and `visible` gates, with the same rule CnContextMenu uses.\n\t\t * An action without either always shows.\n\t\t *\n\t\t * @return {Array} Visible actions for the current row.\n\t\t */\n\t\tvisibleActions() {\n\t\t\treturn this.actions.filter((action) => isRowActionVisible(action, this.row))\n\t\t},\n\n\t\t/**\n\t\t * Visible actions paired with the link each renders as (null for a button).\n\t\t *\n\t\t * @return {Array<{action: object, link: object|null}>}\n\t\t */\n\t\trenderedActions() {\n\t\t\treturn this.visibleActions.map((action) => ({\n\t\t\t\taction,\n\t\t\t\tlink: this.isDisabled(action) ? null : resolveItemActionLink(action, this.row, this.$router),\n\t\t\t}))\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve disabled state for an action — supports both boolean and function.\n\t\t *\n\t\t * @param {object} action - The action definition\n\t\t * @return {boolean} Whether the action is disabled\n\t\t */\n\t\tisDisabled(action) {\n\t\t\tif (typeof action.disabled === 'function') {\n\t\t\t\treturn action.disabled(this.row)\n\t\t\t}\n\t\t\treturn !!action.disabled\n\t\t},\n\n\t\t/**\n\t\t * Resolve the title (native tooltip) for an action — supports both\n\t\t * string and function forms. Returns undefined when no title is\n\t\t * provided so the attribute is not rendered.\n\t\t *\n\t\t * @param {object} action - The action definition\n\t\t * @return {string|undefined} The resolved tooltip text, or undefined.\n\t\t */\n\t\tgetTitle(action) {\n\t\t\tif (typeof action.title === 'function') {\n\t\t\t\treturn action.title(this.row) || undefined\n\t\t\t}\n\t\t\treturn action.title || undefined\n\t\t},\n\n\t\tonAction(action) {\n\t\t\tif (action.handler && typeof action.handler === 'function') {\n\t\t\t\taction.handler(this.row)\n\t\t\t}\n\t\t\tthis.$emit('action', rowActionPayload(action, this.row))\n\t\t},\n\n\t\t/**\n\t\t * A link entry was clicked: route a plain in-app click, leave the rest to\n\t\t * the browser, and emit `action` as a button would. The `handler` is not\n\t\t * called, since navigating is what the link already does. A modified\n\t\t * click opens a new tab, so nothing is emitted in this one.\n\t\t *\n\t\t * @param {object} action The action definition.\n\t\t * @param {object} link The resolved link.\n\t\t * @param {MouseEvent} event The click event.\n\t\t */\n\t\tonLinkAction(action, link, event) {\n\t\t\tif (isModifiedClick(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfollowItemActionLink(event, link, this.$router)\n\t\t\t/**\n\t\t\t * @event action User picked an entry. Payload: `action` (the label), the row, the action's `id` when it has one, and `builtin: true` for a CnIndexPage built-in. A button entry has already run its `handler`; a link entry navigates instead, and a modified (new-tab) click on a link emits nothing.\n\t\t\t * @type {{ action: string, row: object|null, id?: string, builtin?: boolean }}\n\t\t\t */\n\t\t\tthis.$emit('action', rowActionPayload(action, this.row))\n\t\t},\n\n\t\t/**\n\t\t * Slugify an action label for use in stable `data-testid` selectors.\n\t\t * Lowercase, kebab-case, strip non-alphanumeric.\n\t\t *\n\t\t * @param {string} label - The action's display label\n\t\t * @return {string} kebab-case slug suitable for a testid suffix.\n\t\t */\n\t\tslugifyLabel(label) {\n\t\t\treturn slugifyActionLabel(label)\n\t\t},\n\n\t\t/**\n\t\t * The entry's `data-testid`: `cn-action-item-<id>` for a built-in, the label slug otherwise.\n\t\t *\n\t\t * @param {object} action The action definition.\n\t\t * @return {string} The testid.\n\t\t */\n\t\tactionTestId(action) {\n\t\t\treturn rowActionTestId(action)\n\t\t},\n\n\t\t/**\n\t\t * The entry's render key: `builtin:<id>` for a built-in, the label otherwise.\n\t\t *\n\t\t * @param {object} action The action definition.\n\t\t * @return {string} The key.\n\t\t */\n\t\tactionKey(action) {\n\t\t\treturn rowActionKey(action)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-row-action--destructive {\n\tcolor: var(--color-error) !important;\n}\n</style>\n"],"names":["_createBlock","_openBlock","_createElementBlock","_Fragment","_renderList","_normalizeClass","_toDisplayString","_resolveDynamicComponent"],"mappings":";;;;;;;;uBACCA,WAoCW,CAAA,oBAAA,EAAA;IApCC,SAAS,EAAE,QAAc,CAAA,cAAA,CAAC,MAAK,GAAA,CAAA;AACzC,IAAA,OAAO,EAAE,MAAO,CAAA,OAAA;AAChB,IAAA,QAAQ,EAAE,MAAQ,CAAA,QAAA;AACnB,IAAA,aAAW,EAAC,gBAAgB;;qBAClB,MAA4C;AAAtD,OAAAC,SAAA,CAAA,IAAA,CAAA,EAAAC,kBAAA,CA+BUC,QA/B2B,EAAA,IAAA,EAAAC,UAAA,CAAA,QAAA,CAAA,eAAe,EAAjC,CAAA,EAAA,MAAM,EAAE,IAAG,EAAA,KAAA;;AAA8B,UAAA,GAAA,EAAA,QAAA,CAAA,SAAS,CAAC,MAAM,CAAA;;UAGpE,IAAI;4BADXJ,WAcc,CAAA,uBAAA,EAAA;;gBAZZ,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,KAAK,EAAE,QAAQ,CAAA,QAAA,CAAC,MAAM,CAAA;gBACtB,KAAK,EAAAK,cAAA,CAAA,EAAA,4BAAA,EAAkC,MAAM,CAAC,WAAU,EAAA,CAAA;gBACxD,aAAW,EAAE,QAAY,CAAA,YAAA,CAAC,MAAM,CAAA;AACjC,gBAAA,eAAc,EAAd,EAAc;AACb,gBAAA,OAAK,aAAE,QAAY,CAAA,YAAA,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAA,CAAA;;iCAI/B,MACV;kCADU,GACV,GAAAC,eAAA,CAAG,MAAM,CAAC;;;;AAJM,gBAAA,CAAA,MAAM,CAAC,IAAI;;4BAAG,MAAI;kCACjC,MAA+E;AAA1D,wBAAA,CAAA,OAAA,MAAM,CAAC,IAAK,KAAA,QAAA;0CAAjCN,WAA+E,CAAA,iBAAA,EAAA;;8BAA/B,IAAI,EAAE,MAAM,CAAC,IAAI;AAAG,8BAAA,IAAI,EAAE,EAAE;;0CAC5EA,WAAgD,CAAAO,uBAAA,CAAhC,MAAM,CAAC,IAAI,CAAA,EAAA;;AAAU,8BAAA,IAAI,EAAE,EAAE;;;;;;;4BAI/CP,WAagB,CAAA,yBAAA,EAAA;;gBAXd,KAAK,EAAE,QAAQ,CAAA,QAAA,CAAC,MAAM,CAAA;gBACtB,QAAQ,EAAE,QAAU,CAAA,UAAA,CAAC,MAAM,CAAA;gBAC3B,KAAK,EAAAK,cAAA,CAAA,EAAA,4BAAA,EAAkC,MAAM,CAAC,WAAU,EAAA,CAAA;gBACxD,aAAW,EAAE,QAAY,CAAA,YAAA,CAAC,MAAM,CAAA;AACjC,gBAAA,eAAc,EAAd,EAAc;gBACb,OAAK,EAAA,MAAA,KAAE,QAAQ,CAAA,QAAA,CAAC,MAAM,CAAA,CAAA;;iCAIb,MACV;kCADU,GACV,GAAAC,eAAA,CAAG,MAAM,CAAC;;;;AAJM,gBAAA,CAAA,MAAM,CAAC,IAAI;;4BAAG,MAAI;kCACjC,MAA+E;AAA1D,wBAAA,CAAA,OAAA,MAAM,CAAC,IAAK,KAAA,QAAA;0CAAjCN,WAA+E,CAAA,iBAAA,EAAA;;8BAA/B,IAAI,EAAE,MAAM,CAAC,IAAI;AAAG,8BAAA,IAAI,EAAE,EAAE;;0CAC5EA,WAAgD,CAAAO,uBAAA,CAAhC,MAAM,CAAC,IAAI,CAAA,EAAA;;AAAU,8BAAA,IAAI,EAAE,EAAE;;;;;;;;;;;;;;;;"}
@@ -441,9 +441,15 @@ var script = {
441
441
  value = this.choiceLabel(step, raw);
442
442
  }
443
443
  } else if (step.type === 'config-fields') {
444
+ // Only the fields the reader filled in: a row of empty
445
+ // "Name: , KvK: ," reads as broken, not as "not set".
444
446
  value = this.fieldsFor(step)
445
- .map((f) => `${f.label}: ${this.configModel[f.key] !== null && this.configModel[f.key] !== undefined ? this.configModel[f.key] : ''}`)
446
- .join(', ');
447
+ .filter((f) => {
448
+ const v = this.configModel[f.key];
449
+ return v !== null && v !== undefined && String(v).trim() !== ''
450
+ })
451
+ .map((f) => `${f.label}: ${this.configModel[f.key]}`)
452
+ .join(', ') || translate('nextcloud-vue', 'Not set');
447
453
  }
448
454
  if (this.isOnDemand(step)) {
449
455
  const ran = this.ranThisSession(step.id);