@conduction/nextcloud-vue 2.56.0-beta.14 → 2.56.0-beta.15
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/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/CnIndexPage/CnIndexPage.vue2.js +58 -48
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -2
- 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/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/utils/resolveRowActions.js +130 -0
- package/dist/esm/utils/resolveRowActions.js.map +1 -0
- package/dist/esm/utils/rowActionItem.js +99 -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 +469 -130
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/package.json +1 -1
- package/scripts/.jsdoc-baselines.json +2 -2
- package/src/components/CnContextMenu/CnContextMenu.vue +40 -29
- package/src/components/CnIndexPage/CnIndexPage.md +21 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +61 -51
- package/src/components/CnIndexPage/defaultActions.js +6 -7
- package/src/components/CnRowActions/CnRowActions.vue +36 -37
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +3 -2
- package/src/composables/useContextMenu.js +4 -2
- package/src/schemas/app-manifest-v2.schema.json +46 -8
- package/src/utils/resolveRowActions.js +127 -0
- package/src/utils/rowActionItem.js +95 -0
- package/src/utils/timelineEvents.js +43 -0
- package/src/utils/validateManifest.js +60 -3
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -2,6 +2,7 @@ import { NcActions, NcActionButton, NcActionLink } from '@nextcloud/vue';
|
|
|
2
2
|
import { CTX_MENU_DATA_ATTR, CTX_MENU_POPPER_ATTR } from '../../composables/useContextMenu.js';
|
|
3
3
|
import { resolveItemActionLink, followItemActionLink } from '../../utils/actionLink.js';
|
|
4
4
|
import { isModifiedClick } from '../../utils/linkNavigation.js';
|
|
5
|
+
import { isRowActionVisible, slugifyActionLabel, rowActionTestId, rowActionKey, rowActionPayload } from '../../utils/rowActionItem.js';
|
|
5
6
|
import __sfc_main from '../CnIcon/CnIcon.vue.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -16,12 +17,13 @@ import __sfc_main from '../CnIcon/CnIcon.vue.js';
|
|
|
16
17
|
* cursor positioning). The composable handles the DOM attributes; this component
|
|
17
18
|
* handles the NcActions template boilerplate.
|
|
18
19
|
*
|
|
19
|
-
* Dynamic actions (CnIndexPage pattern)
|
|
20
|
+
* Dynamic actions (CnIndexPage pattern).
|
|
21
|
+
* CnIndexPage passes the same per-row list its row actions menu renders, so the two menus always match.
|
|
20
22
|
* ```vue
|
|
21
23
|
* <CnContextMenu
|
|
22
24
|
* v-model:open="contextMenuOpen"
|
|
23
|
-
* :actions="
|
|
24
|
-
* :target-item="
|
|
25
|
+
* :actions="rowActionsFor(contextMenuShownRow)"
|
|
26
|
+
* :target-item="contextMenuShownRow"
|
|
25
27
|
* @action="$emit('action', $event)"
|
|
26
28
|
* @close="closeContextMenu" />
|
|
27
29
|
* ```
|
|
@@ -68,13 +70,15 @@ var script = {
|
|
|
68
70
|
* `visible` (boolean | (targetItem) => boolean) hides the entry when falsy
|
|
69
71
|
* (default: shown). `title` (string | (targetItem) => string) renders as
|
|
70
72
|
* a native tooltip — useful for explaining why an entry is disabled.
|
|
73
|
+
* A locally decidable `visibleWhen` that is false for the target hides the entry too, as in CnRowActions.
|
|
74
|
+
* `id` is passed in the `action` payload; `builtin` marks a CnIndexPage built-in, whose testid is `cn-action-item-<id>` in every locale.
|
|
71
75
|
* `href` (URL) or `to` (vue-router location), each a value or a
|
|
72
76
|
* `(targetItem) => …` function, render the entry as a real link, with
|
|
73
77
|
* `linkTarget` as its `target`; a link emits `action` but does not call
|
|
74
78
|
* `handler`. When the entire array is empty (or all entries are filtered
|
|
75
79
|
* out), only the default slot content is rendered.
|
|
76
80
|
*
|
|
77
|
-
* @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}>}
|
|
81
|
+
* @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}>}
|
|
78
82
|
*/
|
|
79
83
|
actions: {
|
|
80
84
|
type: Array,
|
|
@@ -116,21 +120,13 @@ var script = {
|
|
|
116
120
|
|
|
117
121
|
computed: {
|
|
118
122
|
/**
|
|
119
|
-
* Filter actions by their `visible`
|
|
120
|
-
*
|
|
123
|
+
* Filter actions by their `visibleWhen` and `visible` gates, with the same rule CnRowActions uses.
|
|
124
|
+
* Entries without either always show.
|
|
121
125
|
*
|
|
122
126
|
* @return {Array} Visible actions for the current targetItem.
|
|
123
127
|
*/
|
|
124
128
|
visibleActions() {
|
|
125
|
-
return this.actions.filter((action) =>
|
|
126
|
-
if (action.visible === undefined) {
|
|
127
|
-
return true
|
|
128
|
-
}
|
|
129
|
-
if (typeof action.visible === 'function') {
|
|
130
|
-
return !!action.visible(this.targetItem)
|
|
131
|
-
}
|
|
132
|
-
return !!action.visible
|
|
133
|
-
})
|
|
129
|
+
return this.actions.filter((action) => isRowActionVisible(action, this.targetItem))
|
|
134
130
|
},
|
|
135
131
|
|
|
136
132
|
/**
|
|
@@ -373,18 +369,33 @@ var script = {
|
|
|
373
369
|
|
|
374
370
|
/**
|
|
375
371
|
* Slugify an action label for use in stable `data-testid` selectors.
|
|
376
|
-
* Lowercase, kebab-case, strip non-alphanumeric.
|
|
377
|
-
* `:data-testid` binding on NcActionButton — does not affect runtime
|
|
378
|
-
* behaviour or rendered text.
|
|
372
|
+
* Lowercase, kebab-case, strip non-alphanumeric.
|
|
379
373
|
*
|
|
380
374
|
* @param {string} label The action's display label.
|
|
381
375
|
* @return {string} kebab-case slug suitable for a testid suffix.
|
|
382
376
|
*/
|
|
383
377
|
slugifyLabel(label) {
|
|
384
|
-
return
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
378
|
+
return slugifyActionLabel(label)
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* The entry's `data-testid`: `cn-action-item-<id>` for a built-in, the label slug otherwise.
|
|
383
|
+
*
|
|
384
|
+
* @param {object} action The action descriptor.
|
|
385
|
+
* @return {string} The testid.
|
|
386
|
+
*/
|
|
387
|
+
actionTestId(action) {
|
|
388
|
+
return rowActionTestId(action)
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* The entry's render key: `builtin:<id>` for a built-in, the label otherwise.
|
|
393
|
+
*
|
|
394
|
+
* @param {object} action The action descriptor.
|
|
395
|
+
* @return {string} The key.
|
|
396
|
+
*/
|
|
397
|
+
actionKey(action) {
|
|
398
|
+
return rowActionKey(action)
|
|
388
399
|
},
|
|
389
400
|
|
|
390
401
|
onAction(action) {
|
|
@@ -392,10 +403,10 @@ var script = {
|
|
|
392
403
|
action.handler(this.targetItem);
|
|
393
404
|
}
|
|
394
405
|
/**
|
|
395
|
-
* @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.
|
|
396
|
-
* @type {{ action: string, row: object|null }}
|
|
406
|
+
* @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.
|
|
407
|
+
* @type {{ action: string, row: object|null, id?: string, builtin?: boolean }}
|
|
397
408
|
*/
|
|
398
|
-
this.$emit('action',
|
|
409
|
+
this.$emit('action', rowActionPayload(action, this.targetItem));
|
|
399
410
|
},
|
|
400
411
|
|
|
401
412
|
/**
|
|
@@ -413,7 +424,7 @@ var script = {
|
|
|
413
424
|
return
|
|
414
425
|
}
|
|
415
426
|
followItemActionLink(event, link, this.$router);
|
|
416
|
-
this.$emit('action',
|
|
427
|
+
this.$emit('action', rowActionPayload(action, this.targetItem));
|
|
417
428
|
},
|
|
418
429
|
|
|
419
430
|
onClose() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnContextMenu.vue2.js","sources":["../../../../src/components/CnContextMenu/CnContextMenu.vue"],"sourcesContent":["<template>\n\t<div class=\"cn-context-menu-root\">\n\t\t<NcActions\n\t\t\tv-if=\"!activePanel\"\n\t\t\tref=\"actions\"\n\t\t\tv-model:open=\"internalOpen\"\n\t\t\t:manualOpen=\"true\"\n\t\t\t:forceMenu=\"true\"\n\t\t\tclass=\"cn-context-menu\"\n\t\t\tcontainer=\"body\"\n\t\t\tdata-testid=\"cn-context-menu\"\n\t\t\t@close=\"onClose\"\n\t\t\t@closed=\"onClosed\">\n\t\t\t<!-- Dynamic actions from array prop -->\n\t\t\t<template v-for=\"{ action, link } in renderedActions\" :key=\"action.label\">\n\t\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\t<NcActionLink\n\t\t\t\t\tv-if=\"link\"\n\t\t\t\t\t:href=\"link.href\"\n\t\t\t\t\t:target=\"link.target\"\n\t\t\t\t\t:title=\"resolveTitle(action)\"\n\t\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t\t:data-testid=\"`cn-action-item-${slugifyLabel(action.label)}`\"\n\t\t\t\t\tcloseAfterClick\n\t\t\t\t\t@click=\"onLinkAction(action, link, $event)\">\n\t\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcActionLink>\n\t\t\t\t<NcActionButton\n\t\t\t\t\tv-else\n\t\t\t\t\t:title=\"resolveTitle(action)\"\n\t\t\t\t\t:disabled=\"resolveDisabled(action)\"\n\t\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t\t:data-testid=\"`cn-action-item-${slugifyLabel(action.label)}`\"\n\t\t\t\t\tcloseAfterClick\n\t\t\t\t\t@click=\"onAction(action)\">\n\t\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcActionButton>\n\t\t\t</template>\n\n\t\t\t<!--\n\t\t\t@slot default\n\t\t\t@description Custom NcActionButton-family content rendered inside the\n\t\t\tNcActions menu. Use this for hardcoded buttons (Keepiq pattern) when\n\t\t\tthe `actions` prop is empty.\n\t\t-->\n\t\t\t<slot />\n\t\t</NcActions>\n\n\t\t<!-- CUSTOM PANEL — bypass NcActions, render arbitrary slot content\n\t\t anchored at the cursor. The `#panel:<name>` slot may contain any\n\t\t markup (grids, inputs, custom components) without the NcActions\n\t\t child-allowlist filter. -->\n\t\t<template v-if=\"activePanel && internalOpen\">\n\t\t\t<div\n\t\t\t\tclass=\"cn-context-menu__backdrop\"\n\t\t\t\t@click=\"onClose\"\n\t\t\t\t@contextmenu.prevent=\"onClose\" />\n\t\t\t<div\n\t\t\t\tref=\"panel\"\n\t\t\t\tclass=\"cn-context-menu__panel\"\n\t\t\t\trole=\"menu\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\tdata-testid=\"cn-context-menu-panel\"\n\t\t\t\t:data-panel=\"activePanel\"\n\t\t\t\t@keydown.esc.stop=\"onClose\"\n\t\t\t\t@click.stop>\n\t\t\t\t<!-- @slot panel:<name> Free-form custom panel content shown when\n\t\t\t\t `activePanel === '<name>'`. Bypasses the NcActions child\n\t\t\t\t allowlist — put any markup here (grids, inputs, custom\n\t\t\t\t components, etc.). The slot name is dynamic: define one\n\t\t\t\t `#panel:<name>` per panel you want to support. -->\n\t\t\t\t<!-- @binding {Function} back Clear `activePanel`, returning to the default action list. -->\n\t\t\t\t<!-- @binding {Function} close Close the entire menu, equivalent to clicking outside. -->\n\t\t\t\t<!-- @binding {*} targetItem The right-clicked item, forwarded from the `targetItem` prop. -->\n\t\t\t\t<slot\n\t\t\t\t\t:name=\"`panel:${activePanel}`\"\n\t\t\t\t\t:back=\"back\"\n\t\t\t\t\t:close=\"onClose\"\n\t\t\t\t\t:targetItem=\"targetItem\" />\n\t\t\t</div>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { NcActionButton, NcActionLink, NcActions } from '@nextcloud/vue'\nimport { CTX_MENU_DATA_ATTR, CTX_MENU_POPPER_ATTR } from '../../composables/useContextMenu.js'\nimport { followItemActionLink, resolveItemActionLink } from '../../utils/actionLink.js'\nimport { isModifiedClick } from '../../utils/linkNavigation.js'\nimport { CnIcon } from '../CnIcon/index.js'\n\n/**\n * CnContextMenu — Right-click context menu wrapper around NcActions.\n *\n * Provides a pre-configured NcActions instance that positions itself at the\n * cursor via the `useContextMenu` composable's CSS custom properties. Accepts\n * an `actions` array for the common dynamic case (like CnRowActions), and a\n * default slot for hardcoded NcActionButton content.\n *\n * Pair with `useContextMenu()` for state management (open/close, target item,\n * cursor positioning). The composable handles the DOM attributes; this component\n * handles the NcActions template boilerplate.\n *\n * Dynamic actions (CnIndexPage pattern)\n * ```vue\n * <CnContextMenu\n * v-model:open=\"contextMenuOpen\"\n * :actions=\"mergedActions\"\n * :target-item=\"contextMenuRow\"\n * @action=\"$emit('action', $event)\"\n * @close=\"closeContextMenu\" />\n * ```\n *\n * Custom buttons via slot (Keepiq pattern)\n * ```vue\n * <CnContextMenu\n * v-model:open=\"contextMenuOpen\"\n * @close=\"closeContextMenu\">\n * <NcActionButton close-after-click @click=\"onRename\">\n * <template #icon><PencilIcon :size=\"20\" /></template>\n * Rename\n * </NcActionButton>\n * </CnContextMenu>\n * ```\n */\nexport default {\n\tname: 'CnContextMenu',\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 * Whether the context menu is open. Bind with `v-model:open` — the `.sync`\n\t\t * modifier it replaces was removed in Vue 3 and binds nothing.\n\t\t * Bind to `useContextMenu().isOpen`.\n\t\t */\n\t\topen: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Action definitions rendered as NcActionButton items.\n\t\t * Same format as CnRowActions: `{ label, icon?, handler?, disabled?, visible?, title?, destructive? }`.\n\t\t * `icon` accepts either a component (rendered directly) or a string —\n\t\t * a string is treated as an MDI name and rendered via `CnIcon` (e.g.\n\t\t * `\"Eye\"`), which lets manifest (JSON) actions declare icons by name.\n\t\t * `visible` (boolean | (targetItem) => boolean) hides the entry when falsy\n\t\t * (default: shown). `title` (string | (targetItem) => string) renders as\n\t\t * a native tooltip — useful for explaining why an entry is disabled.\n\t\t * `href` (URL) or `to` (vue-router location), each a value or a\n\t\t * `(targetItem) => …` function, render the entry as a real link, with\n\t\t * `linkTarget` as its `target`; a link emits `action` but does not call\n\t\t * `handler`. When the entire array is empty (or all entries are filtered\n\t\t * out), only the default slot content is rendered.\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/**\n\t\t * The right-clicked item (row, folder, etc.). Passed to action `handler`\n\t\t * and `disabled` callbacks, and included in the `action` event payload.\n\t\t * Bind to `useContextMenu().targetItem`.\n\t\t */\n\t\ttargetItem: {\n\t\t\ttype: [Object, String, Number],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Name of the currently-active custom panel. When set (and the menu is\n\t\t * open), the default NcActions list is replaced by the matching\n\t\t * `#panel:<name>` slot, rendered free-form (no NcActions child filter).\n\t\t * Bind with `v-model:active-panel` — the component emits `update:activePanel(null)` when\n\t\t * the panel's `back()` binding is invoked or the menu closes.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tactivePanel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['action', 'close', 'closed', 'update:activePanel', 'update:open'],\n\n\tdata() {\n\t\treturn {\n\t\t\tinternalOpen: this.open,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Filter actions by their `visible` predicate. Entries without\n\t\t * `visible` are always shown (backwards compatible).\n\t\t *\n\t\t * @return {Array} Visible actions for the current targetItem.\n\t\t */\n\t\tvisibleActions() {\n\t\t\treturn this.actions.filter((action) => {\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.targetItem)\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.resolveDisabled(action) ? null : resolveItemActionLink(action, this.targetItem, this.$router),\n\t\t\t}))\n\t\t},\n\t},\n\n\twatch: {\n\t\topen(val) {\n\t\t\tthis.internalOpen = val\n\t\t},\n\n\t\tinternalOpen(val) {\n\t\t\t// Description goes ABOVE `@event`, not inline after it:\n\t\t\t// vue-docgen-api's event-name splitter stops at the first `:`, so\n\t\t\t// `@event update:open <description>` is read as one long event NAME\n\t\t\t// and the generated docs show an empty description.\n\t\t\t/**\n\t\t\t * Fired whenever the menu's internal open state flips. Used by\n\t\t\t * callers that bind `v-model:open` to track visibility.\n\t\t\t *\n\t\t\t * @event update:open\n\t\t\t * @type {boolean}\n\t\t\t */\n\t\t\tthis.$emit('update:open', val)\n\n\t\t\tif (val) {\n\t\t\t\tthis.tagPopper()\n\t\t\t\tthis.startOutsideWatch()\n\t\t\t} else {\n\t\t\t\tthis.stopOutsideWatch()\n\t\t\t\t// NcActions' `@closed` never fires (see `onClosed`), so drop the\n\t\t\t\t// \"a context menu is open\" attribute here. The CSS vars stay put\n\t\t\t\t// — the hide animation still needs the transform, and the next\n\t\t\t\t// `open()` overwrites them before the popper shows again.\n\t\t\t\tdocument.documentElement.removeAttribute(CTX_MENU_DATA_ATTR)\n\t\t\t}\n\t\t},\n\t},\n\n\tmounted() {\n\t\t// NcActions renders its own NcButton as the popover trigger; the menu\n\t\t// opens only via right-click (via `:open.sync`), so the button is\n\t\t// offscreen-positioned (see the `.cn-context-menu` rule below).\n\t\t//\n\t\t// Use `inert` rather than `aria-hidden`: when the menu is dismissed\n\t\t// without picking an item, NcPopover's focus-trap restores focus to the\n\t\t// trigger button. With `aria-hidden` that produces a \"Blocked\n\t\t// aria-hidden on an element because its descendant retained focus\"\n\t\t// warning (focused element must not be hidden from AT). `inert` both\n\t\t// hides the button from the accessibility tree *and* makes it\n\t\t// unfocusable, so the trap's focus restore falls back to <body> instead\n\t\t// of landing on a hidden element. (The button never needs focus — the\n\t\t// menu opens via `:open.sync`, not by focusing the trigger.)\n\t\tconst trigger = this.$el?.querySelector('.action-item__menutoggle')\n\t\tif (trigger) {\n\t\t\ttrigger.setAttribute('inert', '')\n\t\t}\n\n\t\t// Silence NcPopover's dev-mode a11y warning for this specific instance.\n\t\t//\n\t\t// NcPopover (when `window.OC?.debug` is on) calls\n\t\t// `tabbable(triggerContainer)[0]` on every open and warns\n\t\t// \"It looks like you are using a custom button as a <NcPopover>\n\t\t// trigger…\" if nothing tabbable is found. We *are* using NcButton —\n\t\t// the warning's escape hatch — but the button is offscreen-positioned\n\t\t// and tabbable's visibility checks won't reliably find it across\n\t\t// browsers (Chromium's `Element.checkVisibility()` excludes some\n\t\t// offscreen / clipped elements). The advice in the warning (\"bind\n\t\t// #trigger slot attrs\") doesn't apply: NcActions controls its own\n\t\t// trigger and doesn't expose a `#trigger` slot to override.\n\t\t//\n\t\t// Neutralising the check on our own NcPopover instance (one level\n\t\t// down inside NcActions) leaves every other popover untouched and\n\t\t// keeps the check live for real custom triggers elsewhere.\n\t\t//\n\t\t// Vue 3: `this.$children` was removed (it hard-errors under @vue/compat\n\t\t// MODE 3 with \"INSTANCE_CHILDREN compat has been disabled\"), so reach the\n\t\t// NcActions instance through an explicit template ref instead.\n\t\tconst ncActions = this.$refs.actions\n\t\tconst ncPopover = ncActions?.$refs?.popover\n\t\tif (ncPopover) {\n\t\t\tncPopover.checkTriggerA11y = () => {}\n\t\t}\n\n\t\tthis.tagPopper()\n\n\t\t// Mounting with `open` already true never trips the watcher.\n\t\tif (this.internalOpen) {\n\t\t\tthis.startOutsideWatch()\n\t\t}\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.stopOutsideWatch()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Mark this menu's own popper element so the cursor-positioning CSS in\n\t\t * `src/css/context-menu.css` can target it — and nothing else.\n\t\t *\n\t\t * That CSS used to key on the `<html>` data attribute alone, which\n\t\t * matches *every* `.v-popper__popper` in the document: any other popover\n\t\t * alive while a context menu was open (a table row's actions menu, most\n\t\t * visibly) got dragged to the last right-click coordinates.\n\t\t *\n\t\t * The marker has to be applied imperatively — NcActions hardcodes\n\t\t * `popoverBaseClass: 'action-item__popper'` on its NcPopover and spreads\n\t\t * no `$attrs` onto it, so there is no prop to pass one in through.\n\t\t *\n\t\t * It must be an *attribute*, not a class. floating-vue binds a dynamic\n\t\t * `class` on this element (`--shown` / `--hidden` / `--show-from` / …)\n\t\t * and Vue's `patchClass` assigns `el.className` wholesale, so a class\n\t\t * added here is wiped the first time the popper opens — the rule then\n\t\t * matches nothing and the menu renders at its offscreen trigger. Vue\n\t\t * only patches props present in the vnode, so an attribute it never\n\t\t * renders is left alone.\n\t\t *\n\t\t * The element itself is stable: floating-vue keeps one `$_popperNode`\n\t\t * and `appendChild`s it into the container on first show, so marking it\n\t\t * once at `mounted()` holds for the component's lifetime. Re-running on\n\t\t * open is a cheap, idempotent guard for a not-yet-ready ref chain.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\ttagPopper() {\n\t\t\tconst el = this.$refs.actions?.$refs?.popover?.getPopoverContentElement?.()\n\t\t\tel?.setAttribute?.(CTX_MENU_POPPER_ATTR, '')\n\t\t},\n\n\t\t/**\n\t\t * Start watching for a press outside the menu. Driven from both the\n\t\t * `internalOpen` watcher and `mounted()`, because a consumer may mount\n\t\t * with `open` already true — in which case there is no state transition\n\t\t * for the watcher to see.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartOutsideWatch() {\n\t\t\t// nextTick: the contextmenu/click that opened the menu is still\n\t\t\t// propagating and would dismiss it again on the same gesture.\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tif (this.internalOpen) {\n\t\t\t\t\t// CAPTURE. A canvas that swallows its own mousedown never lets it\n\t\t\t\t\t// bubble to document, so a menu opened over one could not be\n\t\t\t\t\t// dismissed by clicking the canvas — Vue Flow's d3-zoom pane calls\n\t\t\t\t\t// stopImmediatePropagation, which is exactly that shape.\n\t\t\t\t\tdocument.addEventListener('mousedown', this.onDocumentMouseDown, true)\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Stop watching for outside presses.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstopOutsideWatch() {\n\t\t\tdocument.removeEventListener('mousedown', this.onDocumentMouseDown, true)\n\t\t},\n\n\t\t/**\n\t\t * Close the menu when the press lands outside it.\n\t\t *\n\t\t * NcActions has to run in `manual-open` mode here (the menu is opened\n\t\t * from a right-click via the `open` prop, not by activating the trigger\n\t\t * button). `@nextcloud/vue` 9 couples that mode to\n\t\t * `noCloseOnClickOutside: this.manualOpen`, and NcPopover derives\n\t\t * `autoHide: !noCloseOnClickOutside && closeOnClickOutside` — so the\n\t\t * popper's own outside-click dismissal is switched off and nothing else\n\t\t * closes the menu. v8 only cleared the popper's open `triggers`, leaving\n\t\t * autoHide intact, which is why dismissing worked before the Vue 3\n\t\t * upgrade. NcActions spreads no `$attrs` onto NcPopover, so the prop\n\t\t * cannot be overridden from outside — hence handling it here.\n\t\t *\n\t\t * @param {MouseEvent} event The document mousedown.\n\t\t * @return {void}\n\t\t */\n\t\tonDocumentMouseDown(event) {\n\t\t\tif (!this.internalOpen || this.isInsideMenu(event.target)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.onClose()\n\t\t},\n\n\t\t/**\n\t\t * Whether a node sits inside the menu's own surface — the custom panel,\n\t\t * or the popper NcActions teleports to `container=\"body\"` (so it is not\n\t\t * inside `this.$el` and `contains()` on the root would miss it).\n\t\t *\n\t\t * @param {EventTarget} target The event target to test.\n\t\t * @return {boolean} True when the target belongs to this menu.\n\t\t */\n\t\tisInsideMenu(target) {\n\t\t\tif (!(target instanceof Node)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (this.$refs.panel?.contains(target)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst content = this.$refs.actions?.$refs?.popover?.getPopoverContentElement?.()\n\t\t\tif (content?.contains(target)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\t// Fallback for when the popover ref chain is unavailable —\n\t\t\t// `action-item__popper` is the base class NcActions gives its popper.\n\t\t\treturn target instanceof Element && !!target.closest('.action-item__popper')\n\t\t},\n\n\t\tresolveDisabled(action) {\n\t\t\tif (typeof action.disabled === 'function') {\n\t\t\t\treturn action.disabled(this.targetItem)\n\t\t\t}\n\t\t\treturn !!action.disabled\n\t\t},\n\n\t\t/**\n\t\t * Resolve the `title` field on an action descriptor — supports both\n\t\t * a static string and a function `(targetItem) => string`. Returns\n\t\t * undefined when no title is provided so the attribute isn't rendered.\n\t\t *\n\t\t * @param {object} action The action descriptor.\n\t\t * @return {string|undefined} The tooltip text, or undefined.\n\t\t */\n\t\tresolveTitle(action) {\n\t\t\tif (typeof action.title === 'function') {\n\t\t\t\treturn action.title(this.targetItem) || undefined\n\t\t\t}\n\t\t\treturn action.title || undefined\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\n\t\tonAction(action) {\n\t\t\tif (action.handler && typeof action.handler === 'function') {\n\t\t\t\taction.handler(this.targetItem)\n\t\t\t}\n\t\t\t/**\n\t\t\t * @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.\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.targetItem })\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 descriptor.\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\tthis.$emit('action', { action: action.label, row: this.targetItem })\n\t\t},\n\n\t\tonClose() {\n\t\t\tthis.internalOpen = false\n\t\t\tif (this.activePanel) {\n\t\t\t\tthis.$emit('update:activePanel', null)\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event close Fired when the menu starts closing (before the popper's hide animation). This is the reliable one — prefer it over `closed`.\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\n\t\t/**\n\t\t * Clear the active panel and return to the default action list.\n\t\t * Exposed to custom panel slots via the `back` scope binding.\n\t\t */\n\t\tback() {\n\t\t\t// Description above the tag — see the note on `update:open`.\n\t\t\t/**\n\t\t\t * Emitted with `null` to clear the active panel — fired by the\n\t\t\t * panel's `back()` binding and whenever the menu closes while a\n\t\t\t * panel is open. Bind with `v-model:active-panel`.\n\t\t\t *\n\t\t\t * @event update:activePanel\n\t\t\t * @type {null}\n\t\t\t */\n\t\t\tthis.$emit('update:activePanel', null)\n\t\t},\n\n\t\t/**\n\t\t * Fires after the popper's hide animation completes.\n\t\t *\n\t\t * IT MUST NOT CLEAR THE CURSOR POSITION, and it used to. This fires LATE\n\t\t * — a whole hide animation late — so a second click on the same target\n\t\t * reopens the menu before it arrives, and the tear-down then strips the\n\t\t * coordinates that reopen had just written. The transform resolves\n\t\t * against undefined vars and the menu lands in the corner, on every open\n\t\t * after the first.\n\t\t *\n\t\t * It was dead code when written: `@nextcloud/vue` 9 bound `onAfterClose`\n\t\t * against an NcPopover that only emitted `afterHide`. 9.11.0 binds\n\t\t * `onAfterHide`, so it runs now, and a tear-down nothing needed became a\n\t\t * bug. Nothing needs it still: positioning is scoped to this menu's own\n\t\t * popper (see `tagPopper`), the data attribute is dropped in the\n\t\t * `internalOpen` watcher, the next `open()` overwrites the vars, and the\n\t\t * composable's unmount hook clears them for good.\n\t\t */\n\t\tonClosed() {\n\t\t\t/**\n\t\t\t * @event closed Intended to fire after the popper's hide animation completes. Does not currently fire — `@nextcloud/vue` 9's NcActions listens for an `afterClose` event NcPopover never emits. Use `close` instead.\n\t\t\t */\n\t\t\tthis.$emit('closed')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* Selector is deliberately two classes deep. `.cn-context-menu` lands on\n NcActions' root, which NcActions itself styles as `.action-item[data-v-…]\n { position: relative; display: inline-block }`. Scoped CSS appends the attr\n to the last compound only, so a bare `.cn-context-menu` tied that rule at\n specificity (0,2,0) and lost on injection order — the trigger stayed\n `position: relative`, so it kept its in-flow box (top/left just offset it\n visually) and showed up as a full-width ~34px-tall phantom row between the\n table and the pagination on CnIndexPage. Adding the parent class makes this\n (0,3,0) so it wins regardless of order. */\n.cn-context-menu-root > .cn-context-menu {\n\t/* Hide the NcActions trigger button — menu opens only via right-click.\n\t Off-screen rather than display:none / visibility:hidden so NcPopover's\n\t a11y check (`tabbable(triggerContainer)[0]`) still finds the NcButton\n\t and the \"custom button as a <NcPopover> trigger\" warning stays quiet.\n\t Avoid `clip` / `clip-path` here — modern browsers' `Element.checkVisibility()`,\n\t which tabbable calls into, treats those as invisible. Pure offscreen\n\t positioning leaves the element fully \"visible\" to layout while being\n\t off the user's screen. */\n\tposition: absolute;\n\ttop: -9999px;\n\tleft: -9999px;\n\topacity: 0;\n\tpointer-events: none;\n}\n\n.cn-context-menu__backdrop {\n\tposition: fixed;\n\tinset: 0;\n\tz-index: 9998;\n}\n\n.cn-context-menu__panel {\n\tposition: fixed;\n\ttop: var(--cn-ctx-menu-y, 0);\n\tleft: var(--cn-ctx-menu-x, 0);\n\tz-index: 9999;\n\tmin-width: 220px;\n\tpadding: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 12px);\n\tbox-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);\n\toutline: none;\n}\n</style>\n"],"names":["CnIcon"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnContextMenu.vue2.js","sources":["../../../../src/components/CnContextMenu/CnContextMenu.vue"],"sourcesContent":["<template>\n\t<div class=\"cn-context-menu-root\">\n\t\t<NcActions\n\t\t\tv-if=\"!activePanel\"\n\t\t\tref=\"actions\"\n\t\t\tv-model:open=\"internalOpen\"\n\t\t\t:manualOpen=\"true\"\n\t\t\t:forceMenu=\"true\"\n\t\t\tclass=\"cn-context-menu\"\n\t\t\tcontainer=\"body\"\n\t\t\tdata-testid=\"cn-context-menu\"\n\t\t\t@close=\"onClose\"\n\t\t\t@closed=\"onClosed\">\n\t\t\t<!-- Dynamic actions from array prop -->\n\t\t\t<template v-for=\"{ action, link } in renderedActions\" :key=\"actionKey(action)\">\n\t\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\t<NcActionLink\n\t\t\t\t\tv-if=\"link\"\n\t\t\t\t\t:href=\"link.href\"\n\t\t\t\t\t:target=\"link.target\"\n\t\t\t\t\t:title=\"resolveTitle(action)\"\n\t\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t\t:data-testid=\"actionTestId(action)\"\n\t\t\t\t\tcloseAfterClick\n\t\t\t\t\t@click=\"onLinkAction(action, link, $event)\">\n\t\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcActionLink>\n\t\t\t\t<NcActionButton\n\t\t\t\t\tv-else\n\t\t\t\t\t:title=\"resolveTitle(action)\"\n\t\t\t\t\t:disabled=\"resolveDisabled(action)\"\n\t\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t\t:data-testid=\"actionTestId(action)\"\n\t\t\t\t\tcloseAfterClick\n\t\t\t\t\t@click=\"onAction(action)\">\n\t\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcActionButton>\n\t\t\t</template>\n\n\t\t\t<!--\n\t\t\t@slot default\n\t\t\t@description Custom NcActionButton-family content rendered inside the\n\t\t\tNcActions menu. Use this for hardcoded buttons (Keepiq pattern) when\n\t\t\tthe `actions` prop is empty.\n\t\t-->\n\t\t\t<slot />\n\t\t</NcActions>\n\n\t\t<!-- CUSTOM PANEL — bypass NcActions, render arbitrary slot content\n\t\t anchored at the cursor. The `#panel:<name>` slot may contain any\n\t\t markup (grids, inputs, custom components) without the NcActions\n\t\t child-allowlist filter. -->\n\t\t<template v-if=\"activePanel && internalOpen\">\n\t\t\t<div\n\t\t\t\tclass=\"cn-context-menu__backdrop\"\n\t\t\t\t@click=\"onClose\"\n\t\t\t\t@contextmenu.prevent=\"onClose\" />\n\t\t\t<div\n\t\t\t\tref=\"panel\"\n\t\t\t\tclass=\"cn-context-menu__panel\"\n\t\t\t\trole=\"menu\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\tdata-testid=\"cn-context-menu-panel\"\n\t\t\t\t:data-panel=\"activePanel\"\n\t\t\t\t@keydown.esc.stop=\"onClose\"\n\t\t\t\t@click.stop>\n\t\t\t\t<!-- @slot panel:<name> Free-form custom panel content shown when\n\t\t\t\t `activePanel === '<name>'`. Bypasses the NcActions child\n\t\t\t\t allowlist — put any markup here (grids, inputs, custom\n\t\t\t\t components, etc.). The slot name is dynamic: define one\n\t\t\t\t `#panel:<name>` per panel you want to support. -->\n\t\t\t\t<!-- @binding {Function} back Clear `activePanel`, returning to the default action list. -->\n\t\t\t\t<!-- @binding {Function} close Close the entire menu, equivalent to clicking outside. -->\n\t\t\t\t<!-- @binding {*} targetItem The right-clicked item, forwarded from the `targetItem` prop. -->\n\t\t\t\t<slot\n\t\t\t\t\t:name=\"`panel:${activePanel}`\"\n\t\t\t\t\t:back=\"back\"\n\t\t\t\t\t:close=\"onClose\"\n\t\t\t\t\t:targetItem=\"targetItem\" />\n\t\t\t</div>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { NcActionButton, NcActionLink, NcActions } from '@nextcloud/vue'\nimport { CTX_MENU_DATA_ATTR, CTX_MENU_POPPER_ATTR } from '../../composables/useContextMenu.js'\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 * CnContextMenu — Right-click context menu wrapper around NcActions.\n *\n * Provides a pre-configured NcActions instance that positions itself at the\n * cursor via the `useContextMenu` composable's CSS custom properties. Accepts\n * an `actions` array for the common dynamic case (like CnRowActions), and a\n * default slot for hardcoded NcActionButton content.\n *\n * Pair with `useContextMenu()` for state management (open/close, target item,\n * cursor positioning). The composable handles the DOM attributes; this component\n * handles the NcActions template boilerplate.\n *\n * Dynamic actions (CnIndexPage pattern).\n * CnIndexPage passes the same per-row list its row actions menu renders, so the two menus always match.\n * ```vue\n * <CnContextMenu\n * v-model:open=\"contextMenuOpen\"\n * :actions=\"rowActionsFor(contextMenuShownRow)\"\n * :target-item=\"contextMenuShownRow\"\n * @action=\"$emit('action', $event)\"\n * @close=\"closeContextMenu\" />\n * ```\n *\n * Custom buttons via slot (Keepiq pattern)\n * ```vue\n * <CnContextMenu\n * v-model:open=\"contextMenuOpen\"\n * @close=\"closeContextMenu\">\n * <NcActionButton close-after-click @click=\"onRename\">\n * <template #icon><PencilIcon :size=\"20\" /></template>\n * Rename\n * </NcActionButton>\n * </CnContextMenu>\n * ```\n */\nexport default {\n\tname: 'CnContextMenu',\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 * Whether the context menu is open. Bind with `v-model:open` — the `.sync`\n\t\t * modifier it replaces was removed in Vue 3 and binds nothing.\n\t\t * Bind to `useContextMenu().isOpen`.\n\t\t */\n\t\topen: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Action definitions rendered as NcActionButton items.\n\t\t * Same format as CnRowActions: `{ label, icon?, handler?, disabled?, visible?, title?, destructive? }`.\n\t\t * `icon` accepts either a component (rendered directly) or a string —\n\t\t * a string is treated as an MDI name and rendered via `CnIcon` (e.g.\n\t\t * `\"Eye\"`), which lets manifest (JSON) actions declare icons by name.\n\t\t * `visible` (boolean | (targetItem) => boolean) hides the entry when falsy\n\t\t * (default: shown). `title` (string | (targetItem) => string) renders as\n\t\t * a native tooltip — useful for explaining why an entry is disabled.\n\t\t * A locally decidable `visibleWhen` that is false for the target hides the entry too, as in CnRowActions.\n\t\t * `id` is passed in the `action` payload; `builtin` marks a CnIndexPage built-in, whose testid is `cn-action-item-<id>` in every locale.\n\t\t * `href` (URL) or `to` (vue-router location), each a value or a\n\t\t * `(targetItem) => …` function, render the entry as a real link, with\n\t\t * `linkTarget` as its `target`; a link emits `action` but does not call\n\t\t * `handler`. When the entire array is empty (or all entries are filtered\n\t\t * out), only the default slot content is rendered.\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), visibleWhen: object, 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/**\n\t\t * The right-clicked item (row, folder, etc.). Passed to action `handler`\n\t\t * and `disabled` callbacks, and included in the `action` event payload.\n\t\t * Bind to `useContextMenu().targetItem`.\n\t\t */\n\t\ttargetItem: {\n\t\t\ttype: [Object, String, Number],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Name of the currently-active custom panel. When set (and the menu is\n\t\t * open), the default NcActions list is replaced by the matching\n\t\t * `#panel:<name>` slot, rendered free-form (no NcActions child filter).\n\t\t * Bind with `v-model:active-panel` — the component emits `update:activePanel(null)` when\n\t\t * the panel's `back()` binding is invoked or the menu closes.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tactivePanel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['action', 'close', 'closed', 'update:activePanel', 'update:open'],\n\n\tdata() {\n\t\treturn {\n\t\t\tinternalOpen: this.open,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Filter actions by their `visibleWhen` and `visible` gates, with the same rule CnRowActions uses.\n\t\t * Entries without either always show.\n\t\t *\n\t\t * @return {Array} Visible actions for the current targetItem.\n\t\t */\n\t\tvisibleActions() {\n\t\t\treturn this.actions.filter((action) => isRowActionVisible(action, this.targetItem))\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.resolveDisabled(action) ? null : resolveItemActionLink(action, this.targetItem, this.$router),\n\t\t\t}))\n\t\t},\n\t},\n\n\twatch: {\n\t\topen(val) {\n\t\t\tthis.internalOpen = val\n\t\t},\n\n\t\tinternalOpen(val) {\n\t\t\t// Description goes ABOVE `@event`, not inline after it:\n\t\t\t// vue-docgen-api's event-name splitter stops at the first `:`, so\n\t\t\t// `@event update:open <description>` is read as one long event NAME\n\t\t\t// and the generated docs show an empty description.\n\t\t\t/**\n\t\t\t * Fired whenever the menu's internal open state flips. Used by\n\t\t\t * callers that bind `v-model:open` to track visibility.\n\t\t\t *\n\t\t\t * @event update:open\n\t\t\t * @type {boolean}\n\t\t\t */\n\t\t\tthis.$emit('update:open', val)\n\n\t\t\tif (val) {\n\t\t\t\tthis.tagPopper()\n\t\t\t\tthis.startOutsideWatch()\n\t\t\t} else {\n\t\t\t\tthis.stopOutsideWatch()\n\t\t\t\t// NcActions' `@closed` never fires (see `onClosed`), so drop the\n\t\t\t\t// \"a context menu is open\" attribute here. The CSS vars stay put\n\t\t\t\t// — the hide animation still needs the transform, and the next\n\t\t\t\t// `open()` overwrites them before the popper shows again.\n\t\t\t\tdocument.documentElement.removeAttribute(CTX_MENU_DATA_ATTR)\n\t\t\t}\n\t\t},\n\t},\n\n\tmounted() {\n\t\t// NcActions renders its own NcButton as the popover trigger; the menu\n\t\t// opens only via right-click (via `:open.sync`), so the button is\n\t\t// offscreen-positioned (see the `.cn-context-menu` rule below).\n\t\t//\n\t\t// Use `inert` rather than `aria-hidden`: when the menu is dismissed\n\t\t// without picking an item, NcPopover's focus-trap restores focus to the\n\t\t// trigger button. With `aria-hidden` that produces a \"Blocked\n\t\t// aria-hidden on an element because its descendant retained focus\"\n\t\t// warning (focused element must not be hidden from AT). `inert` both\n\t\t// hides the button from the accessibility tree *and* makes it\n\t\t// unfocusable, so the trap's focus restore falls back to <body> instead\n\t\t// of landing on a hidden element. (The button never needs focus — the\n\t\t// menu opens via `:open.sync`, not by focusing the trigger.)\n\t\tconst trigger = this.$el?.querySelector('.action-item__menutoggle')\n\t\tif (trigger) {\n\t\t\ttrigger.setAttribute('inert', '')\n\t\t}\n\n\t\t// Silence NcPopover's dev-mode a11y warning for this specific instance.\n\t\t//\n\t\t// NcPopover (when `window.OC?.debug` is on) calls\n\t\t// `tabbable(triggerContainer)[0]` on every open and warns\n\t\t// \"It looks like you are using a custom button as a <NcPopover>\n\t\t// trigger…\" if nothing tabbable is found. We *are* using NcButton —\n\t\t// the warning's escape hatch — but the button is offscreen-positioned\n\t\t// and tabbable's visibility checks won't reliably find it across\n\t\t// browsers (Chromium's `Element.checkVisibility()` excludes some\n\t\t// offscreen / clipped elements). The advice in the warning (\"bind\n\t\t// #trigger slot attrs\") doesn't apply: NcActions controls its own\n\t\t// trigger and doesn't expose a `#trigger` slot to override.\n\t\t//\n\t\t// Neutralising the check on our own NcPopover instance (one level\n\t\t// down inside NcActions) leaves every other popover untouched and\n\t\t// keeps the check live for real custom triggers elsewhere.\n\t\t//\n\t\t// Vue 3: `this.$children` was removed (it hard-errors under @vue/compat\n\t\t// MODE 3 with \"INSTANCE_CHILDREN compat has been disabled\"), so reach the\n\t\t// NcActions instance through an explicit template ref instead.\n\t\tconst ncActions = this.$refs.actions\n\t\tconst ncPopover = ncActions?.$refs?.popover\n\t\tif (ncPopover) {\n\t\t\tncPopover.checkTriggerA11y = () => {}\n\t\t}\n\n\t\tthis.tagPopper()\n\n\t\t// Mounting with `open` already true never trips the watcher.\n\t\tif (this.internalOpen) {\n\t\t\tthis.startOutsideWatch()\n\t\t}\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.stopOutsideWatch()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Mark this menu's own popper element so the cursor-positioning CSS in\n\t\t * `src/css/context-menu.css` can target it — and nothing else.\n\t\t *\n\t\t * That CSS used to key on the `<html>` data attribute alone, which\n\t\t * matches *every* `.v-popper__popper` in the document: any other popover\n\t\t * alive while a context menu was open (a table row's actions menu, most\n\t\t * visibly) got dragged to the last right-click coordinates.\n\t\t *\n\t\t * The marker has to be applied imperatively — NcActions hardcodes\n\t\t * `popoverBaseClass: 'action-item__popper'` on its NcPopover and spreads\n\t\t * no `$attrs` onto it, so there is no prop to pass one in through.\n\t\t *\n\t\t * It must be an *attribute*, not a class. floating-vue binds a dynamic\n\t\t * `class` on this element (`--shown` / `--hidden` / `--show-from` / …)\n\t\t * and Vue's `patchClass` assigns `el.className` wholesale, so a class\n\t\t * added here is wiped the first time the popper opens — the rule then\n\t\t * matches nothing and the menu renders at its offscreen trigger. Vue\n\t\t * only patches props present in the vnode, so an attribute it never\n\t\t * renders is left alone.\n\t\t *\n\t\t * The element itself is stable: floating-vue keeps one `$_popperNode`\n\t\t * and `appendChild`s it into the container on first show, so marking it\n\t\t * once at `mounted()` holds for the component's lifetime. Re-running on\n\t\t * open is a cheap, idempotent guard for a not-yet-ready ref chain.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\ttagPopper() {\n\t\t\tconst el = this.$refs.actions?.$refs?.popover?.getPopoverContentElement?.()\n\t\t\tel?.setAttribute?.(CTX_MENU_POPPER_ATTR, '')\n\t\t},\n\n\t\t/**\n\t\t * Start watching for a press outside the menu. Driven from both the\n\t\t * `internalOpen` watcher and `mounted()`, because a consumer may mount\n\t\t * with `open` already true — in which case there is no state transition\n\t\t * for the watcher to see.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartOutsideWatch() {\n\t\t\t// nextTick: the contextmenu/click that opened the menu is still\n\t\t\t// propagating and would dismiss it again on the same gesture.\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tif (this.internalOpen) {\n\t\t\t\t\t// CAPTURE. A canvas that swallows its own mousedown never lets it\n\t\t\t\t\t// bubble to document, so a menu opened over one could not be\n\t\t\t\t\t// dismissed by clicking the canvas — Vue Flow's d3-zoom pane calls\n\t\t\t\t\t// stopImmediatePropagation, which is exactly that shape.\n\t\t\t\t\tdocument.addEventListener('mousedown', this.onDocumentMouseDown, true)\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Stop watching for outside presses.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstopOutsideWatch() {\n\t\t\tdocument.removeEventListener('mousedown', this.onDocumentMouseDown, true)\n\t\t},\n\n\t\t/**\n\t\t * Close the menu when the press lands outside it.\n\t\t *\n\t\t * NcActions has to run in `manual-open` mode here (the menu is opened\n\t\t * from a right-click via the `open` prop, not by activating the trigger\n\t\t * button). `@nextcloud/vue` 9 couples that mode to\n\t\t * `noCloseOnClickOutside: this.manualOpen`, and NcPopover derives\n\t\t * `autoHide: !noCloseOnClickOutside && closeOnClickOutside` — so the\n\t\t * popper's own outside-click dismissal is switched off and nothing else\n\t\t * closes the menu. v8 only cleared the popper's open `triggers`, leaving\n\t\t * autoHide intact, which is why dismissing worked before the Vue 3\n\t\t * upgrade. NcActions spreads no `$attrs` onto NcPopover, so the prop\n\t\t * cannot be overridden from outside — hence handling it here.\n\t\t *\n\t\t * @param {MouseEvent} event The document mousedown.\n\t\t * @return {void}\n\t\t */\n\t\tonDocumentMouseDown(event) {\n\t\t\tif (!this.internalOpen || this.isInsideMenu(event.target)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.onClose()\n\t\t},\n\n\t\t/**\n\t\t * Whether a node sits inside the menu's own surface — the custom panel,\n\t\t * or the popper NcActions teleports to `container=\"body\"` (so it is not\n\t\t * inside `this.$el` and `contains()` on the root would miss it).\n\t\t *\n\t\t * @param {EventTarget} target The event target to test.\n\t\t * @return {boolean} True when the target belongs to this menu.\n\t\t */\n\t\tisInsideMenu(target) {\n\t\t\tif (!(target instanceof Node)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (this.$refs.panel?.contains(target)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst content = this.$refs.actions?.$refs?.popover?.getPopoverContentElement?.()\n\t\t\tif (content?.contains(target)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\t// Fallback for when the popover ref chain is unavailable —\n\t\t\t// `action-item__popper` is the base class NcActions gives its popper.\n\t\t\treturn target instanceof Element && !!target.closest('.action-item__popper')\n\t\t},\n\n\t\tresolveDisabled(action) {\n\t\t\tif (typeof action.disabled === 'function') {\n\t\t\t\treturn action.disabled(this.targetItem)\n\t\t\t}\n\t\t\treturn !!action.disabled\n\t\t},\n\n\t\t/**\n\t\t * Resolve the `title` field on an action descriptor — supports both\n\t\t * a static string and a function `(targetItem) => string`. Returns\n\t\t * undefined when no title is provided so the attribute isn't rendered.\n\t\t *\n\t\t * @param {object} action The action descriptor.\n\t\t * @return {string|undefined} The tooltip text, or undefined.\n\t\t */\n\t\tresolveTitle(action) {\n\t\t\tif (typeof action.title === 'function') {\n\t\t\t\treturn action.title(this.targetItem) || undefined\n\t\t\t}\n\t\t\treturn action.title || undefined\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 descriptor.\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 descriptor.\n\t\t * @return {string} The key.\n\t\t */\n\t\tactionKey(action) {\n\t\t\treturn rowActionKey(action)\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.targetItem)\n\t\t\t}\n\t\t\t/**\n\t\t\t * @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.\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.targetItem))\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 descriptor.\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\tthis.$emit('action', rowActionPayload(action, this.targetItem))\n\t\t},\n\n\t\tonClose() {\n\t\t\tthis.internalOpen = false\n\t\t\tif (this.activePanel) {\n\t\t\t\tthis.$emit('update:activePanel', null)\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event close Fired when the menu starts closing (before the popper's hide animation). This is the reliable one — prefer it over `closed`.\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\n\t\t/**\n\t\t * Clear the active panel and return to the default action list.\n\t\t * Exposed to custom panel slots via the `back` scope binding.\n\t\t */\n\t\tback() {\n\t\t\t// Description above the tag — see the note on `update:open`.\n\t\t\t/**\n\t\t\t * Emitted with `null` to clear the active panel — fired by the\n\t\t\t * panel's `back()` binding and whenever the menu closes while a\n\t\t\t * panel is open. Bind with `v-model:active-panel`.\n\t\t\t *\n\t\t\t * @event update:activePanel\n\t\t\t * @type {null}\n\t\t\t */\n\t\t\tthis.$emit('update:activePanel', null)\n\t\t},\n\n\t\t/**\n\t\t * Fires after the popper's hide animation completes.\n\t\t *\n\t\t * IT MUST NOT CLEAR THE CURSOR POSITION, and it used to. This fires LATE\n\t\t * — a whole hide animation late — so a second click on the same target\n\t\t * reopens the menu before it arrives, and the tear-down then strips the\n\t\t * coordinates that reopen had just written. The transform resolves\n\t\t * against undefined vars and the menu lands in the corner, on every open\n\t\t * after the first.\n\t\t *\n\t\t * It was dead code when written: `@nextcloud/vue` 9 bound `onAfterClose`\n\t\t * against an NcPopover that only emitted `afterHide`. 9.11.0 binds\n\t\t * `onAfterHide`, so it runs now, and a tear-down nothing needed became a\n\t\t * bug. Nothing needs it still: positioning is scoped to this menu's own\n\t\t * popper (see `tagPopper`), the data attribute is dropped in the\n\t\t * `internalOpen` watcher, the next `open()` overwrites the vars, and the\n\t\t * composable's unmount hook clears them for good.\n\t\t */\n\t\tonClosed() {\n\t\t\t/**\n\t\t\t * @event closed Intended to fire after the popper's hide animation completes. Does not currently fire — `@nextcloud/vue` 9's NcActions listens for an `afterClose` event NcPopover never emits. Use `close` instead.\n\t\t\t */\n\t\t\tthis.$emit('closed')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* Selector is deliberately two classes deep. `.cn-context-menu` lands on\n NcActions' root, which NcActions itself styles as `.action-item[data-v-…]\n { position: relative; display: inline-block }`. Scoped CSS appends the attr\n to the last compound only, so a bare `.cn-context-menu` tied that rule at\n specificity (0,2,0) and lost on injection order — the trigger stayed\n `position: relative`, so it kept its in-flow box (top/left just offset it\n visually) and showed up as a full-width ~34px-tall phantom row between the\n table and the pagination on CnIndexPage. Adding the parent class makes this\n (0,3,0) so it wins regardless of order. */\n.cn-context-menu-root > .cn-context-menu {\n\t/* Hide the NcActions trigger button — menu opens only via right-click.\n\t Off-screen rather than display:none / visibility:hidden so NcPopover's\n\t a11y check (`tabbable(triggerContainer)[0]`) still finds the NcButton\n\t and the \"custom button as a <NcPopover> trigger\" warning stays quiet.\n\t Avoid `clip` / `clip-path` here — modern browsers' `Element.checkVisibility()`,\n\t which tabbable calls into, treats those as invisible. Pure offscreen\n\t positioning leaves the element fully \"visible\" to layout while being\n\t off the user's screen. */\n\tposition: absolute;\n\ttop: -9999px;\n\tleft: -9999px;\n\topacity: 0;\n\tpointer-events: none;\n}\n\n.cn-context-menu__backdrop {\n\tposition: fixed;\n\tinset: 0;\n\tz-index: 9998;\n}\n\n.cn-context-menu__panel {\n\tposition: fixed;\n\ttop: var(--cn-ctx-menu-y, 0);\n\tleft: var(--cn-ctx-menu-x, 0);\n\tz-index: 9999;\n\tmin-width: 220px;\n\tpadding: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 12px);\n\tbox-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);\n\toutline: none;\n}\n</style>\n"],"names":["CnIcon"],"mappings":";;;;;;;AAoGA;;AAEC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;;;AAIA;AACA;AACA;AACA;;AAED,aAAe;AACd,CAAA,IAAI,EAAE,eAAe;;CAErB,UAAU,EAAE;AACX,EAAA,SAAS;AACT,EAAA,cAAc;EACd,YAAY;AACZ,UAAAA,UAAM;EACN;;AAED,CAAA,KAAK,EAAE;AACN;;;AAGC;;EAED,IAAI,EAAE;GACL,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;AAED;;AAEC;;;;AAIA;;;AAGA;;;;;;AAMA;AACA;;;AAGD,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,KAAK;AACX,GAAA,OAAO,EAAE,MAAM,EAAE;GACjB;;AAED;;;AAGC;;EAED,UAAU,EAAE;GACX,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;GAC9B,OAAO,EAAE,IAAI;GACb;;AAED;;;AAGC;AACA;;AAEA;AACA;;EAED,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;EACD;;AAED,CAAA,KAAK,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,aAAa,CAAC;;AAEzE,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;GACN,YAAY,EAAE,IAAI,CAAC,IAAI;AACxB,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;AACC;AACA;AACA;;;AAGD,EAAA,cAAc,GAAG;AAChB,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;GAClF;;AAED;;AAEC;;;AAGD,EAAA,eAAe,GAAG;AACjB,GAAA,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,MAAM,MAAM;AAC3C,IAAA,MAAM;IACN,IAAI,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM,IAAI,IAAG,GAAI,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC;AACzG,IAAC,CAAC,CAAA;GACF;EACD;;AAED,CAAA,KAAK,EAAE;AACN,EAAA,IAAI,CAAC,GAAG,EAAE;AACT,GAAA,IAAI,CAAC,YAAa,GAAE,IAAE;GACtB;;AAED,EAAA,YAAY,CAAC,GAAG,EAAE;;;;AAIjB;AACA;;;AAGC;AACA;AACA;;GAED,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,EAAA;;AAE7B,GAAA,IAAI,GAAG,EAAE;AACR,IAAA,IAAI,CAAC,SAAS,GAAC;IACf,IAAI,CAAC,iBAAiB,GAAC;IACtB,MAAK;IACN,IAAI,CAAC,gBAAgB,GAAC;;;;;IAKtB,QAAQ,CAAC,eAAe,CAAC,eAAe,CAAC,kBAAkB,EAAA;AAC5D,IAAA;GACA;EACD;;AAED,CAAA,OAAO,GAAG;;;;;;;;;;;;;;AAcT,EAAA,MAAM,OAAQ,GAAE,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,0BAA0B,EAAA;EAClE,IAAI,OAAO,EAAE;AACZ,GAAA,OAAO,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,EAAA;AACjC,GAAA;;AAEA;;AAEA;;;;;;;;;;;;;;;AAeA;AACA;;AAEA,EAAA,MAAM,YAAY,IAAI,CAAC,KAAK,CAAC,QAAM;AACnC,EAAA,MAAM,SAAQ,GAAI,SAAS,EAAE,KAAK,EAAE,QAAM;EAC1C,IAAI,SAAS,EAAE;AACd,GAAA,SAAS,CAAC,gBAAe,GAAI,MAAM,GAAC;AACrC,GAAA;;AAEA,EAAA,IAAI,CAAC,SAAS,GAAC;;;AAGf,EAAA,IAAI,IAAI,CAAC,YAAY,EAAE;GACtB,IAAI,CAAC,iBAAiB,GAAC;AACxB,GAAA;EACA;;AAED,CAAA,aAAa,GAAG;EACf,IAAI,CAAC,gBAAgB,GAAC;EACtB;;AAED,CAAA,OAAO,EAAE;AACR;;;AAGC;;AAEA;;;AAGA;;AAEA;;AAEA;;;;;;;AAOA;AACA;;;;;AAKA;;;EAGD,SAAS,GAAG;AACX,GAAA,MAAM,EAAG,GAAE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,wBAAwB,KAAG;AAC1E,GAAA,EAAE,EAAE,YAAY,GAAG,oBAAoB,EAAE,EAAE,EAAA;GAC3C;;AAED;;;;AAIC;AACA;;;AAGD,EAAA,iBAAiB,GAAG;;;AAGnB,GAAA,IAAI,CAAC,SAAS,CAAC,MAAM;AACpB,IAAA,IAAI,IAAI,CAAC,YAAY,EAAE;;;;;AAKtB,KAAA,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,IAAI,CAAC,mBAAmB,EAAE,IAAI,EAAA;AACtE,KAAA;IACA,EAAA;GACD;;AAED;AACC;AACA;;;AAGD,EAAA,gBAAgB,GAAG;AAClB,GAAA,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,IAAI,CAAC,mBAAmB,EAAE,IAAI,EAAA;GACxE;;AAED;AACC;AACA;;;AAGA;;;;;;;;AAQA;AACA;;;EAGD,mBAAmB,CAAC,KAAK,EAAE;GAC1B,IAAI,CAAC,IAAI,CAAC,gBAAgB,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;IAC1D,MAAK;AACN,IAAA;GACA,IAAI,CAAC,OAAO,GAAC;GACb;;AAED;;;;AAIC;;;;AAID,EAAA,YAAY,CAAC,MAAM,EAAE;GACpB,IAAI,EAAE,MAAK,YAAa,IAAI,CAAC,EAAE;IAC9B,OAAO,KAAI;AACZ,IAAA;AACA,GAAA,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE;AACvC,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,MAAM,OAAQ,GAAE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,wBAAwB,KAAG;GAC/E,IAAI,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE;AAC9B,IAAA,OAAO,IAAG;AACX,IAAA;;;AAGA,GAAA,OAAO,MAAK,YAAa,OAAQ,IAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,sBAAsB,CAAA;GAC3E;;EAED,eAAe,CAAC,MAAM,EAAE;AACvB,GAAA,IAAI,OAAO,MAAM,CAAC,QAAO,KAAM,UAAU,EAAE;AAC1C,IAAA,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAA;AACvC,IAAA;GACA,OAAO,CAAC,CAAC,MAAM,CAAC,QAAO;GACvB;;AAED;;;;AAIC;AACA;;;AAGD,EAAA,YAAY,CAAC,MAAM,EAAE;AACpB,GAAA,IAAI,OAAO,MAAM,CAAC,KAAI,KAAM,UAAU,EAAE;AACvC,IAAA,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,SAAQ;AACjD,IAAA;GACA,OAAO,MAAM,CAAC,KAAM,IAAG,SAAQ;GAC/B;;AAED;;AAEC;AACA;AACA;;;AAGD,EAAA,YAAY,CAAC,KAAK,EAAE;AACnB,GAAA,OAAO,kBAAkB,CAAC,KAAK,CAAA;GAC/B;;AAED;AACC;AACA;AACA;;;AAGD,EAAA,YAAY,CAAC,MAAM,EAAE;GACpB,OAAO,eAAe,CAAC,MAAM,CAAA;GAC7B;;AAED;AACC;AACA;AACA;;;AAGD,EAAA,SAAS,CAAC,MAAM,EAAE;GACjB,OAAO,YAAY,CAAC,MAAM,CAAA;GAC1B;;AAED,EAAA,QAAQ,CAAC,MAAM,EAAE;GAChB,IAAI,MAAM,CAAC,OAAM,IAAK,OAAO,MAAM,CAAC,OAAQ,KAAI,UAAU,EAAE;AAC3D,IAAA,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAA;AAC/B,IAAA;AACA;AACC;AACA;;GAED,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,EAAA;GAC9D;;AAED;;;;;AAKC;AACA;AACA;AACA;;AAED,EAAA,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE;GACjC,IAAI,eAAe,CAAC,KAAK,CAAC,EAAE;IAC3B,MAAK;AACN,IAAA;AACA,GAAA,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAA;GAC9C,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,EAAA;GAC9D;;AAED,EAAA,OAAO,GAAG;GACT,IAAI,CAAC,YAAW,GAAI,MAAI;AACxB,GAAA,IAAI,IAAI,CAAC,WAAW,EAAE;AACrB,IAAA,IAAI,CAAC,KAAK,CAAC,oBAAoB,EAAE,IAAI,EAAA;AACtC,IAAA;AACA;;;AAGA,GAAA,IAAI,CAAC,KAAK,CAAC,OAAO,EAAA;GAClB;;AAED;;;;AAIA,EAAA,IAAI,GAAG;;AAEN;;;AAGC;AACA;;;;AAID,GAAA,IAAI,CAAC,KAAK,CAAC,oBAAoB,EAAE,IAAI,EAAA;GACrC;;AAED;AACC;AACA;;;;;;AAMA;AACA;;;;;;;AAOA;;EAED,QAAQ,GAAG;AACV;AACC;;AAED,GAAA,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAA;GACnB;EACD;AACF;;;;"}
|
|
@@ -27,7 +27,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
27
27
|
default: withCtx(() => [
|
|
28
28
|
(openBlock(true), createElementBlock(Fragment, null, renderList($options.renderedActions, ({ action, link }) => {
|
|
29
29
|
return (openBlock(), createElementBlock(Fragment, {
|
|
30
|
-
key: action
|
|
30
|
+
key: $options.actionKey(action)
|
|
31
31
|
}, [
|
|
32
32
|
link
|
|
33
33
|
? (openBlock(), createBlock(_component_NcActionLink, {
|
|
@@ -36,7 +36,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
36
36
|
target: link.target,
|
|
37
37
|
title: $options.resolveTitle(action),
|
|
38
38
|
class: normalizeClass({ 'cn-row-action--destructive': action.destructive }),
|
|
39
|
-
"data-testid":
|
|
39
|
+
"data-testid": $options.actionTestId(action),
|
|
40
40
|
closeAfterClick: "",
|
|
41
41
|
onClick: $event => ($options.onLinkAction(action, link, $event))
|
|
42
42
|
}, createSlots({
|
|
@@ -69,7 +69,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
69
69
|
title: $options.resolveTitle(action),
|
|
70
70
|
disabled: $options.resolveDisabled(action),
|
|
71
71
|
class: normalizeClass({ 'cn-row-action--destructive': action.destructive }),
|
|
72
|
-
"data-testid":
|
|
72
|
+
"data-testid": $options.actionTestId(action),
|
|
73
73
|
closeAfterClick: "",
|
|
74
74
|
onClick: $event => ($options.onAction(action))
|
|
75
75
|
}, createSlots({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnContextMenu.vue3.js","sources":["../../../../src/components/CnContextMenu/CnContextMenu.vue?vue&type=template&id=1bef81d7&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-context-menu-root\">\n\t\t<NcActions\n\t\t\tv-if=\"!activePanel\"\n\t\t\tref=\"actions\"\n\t\t\tv-model:open=\"internalOpen\"\n\t\t\t:manualOpen=\"true\"\n\t\t\t:forceMenu=\"true\"\n\t\t\tclass=\"cn-context-menu\"\n\t\t\tcontainer=\"body\"\n\t\t\tdata-testid=\"cn-context-menu\"\n\t\t\t@close=\"onClose\"\n\t\t\t@closed=\"onClosed\">\n\t\t\t<!-- Dynamic actions from array prop -->\n\t\t\t<template v-for=\"{ action, link } in renderedActions\" :key=\"action.label\">\n\t\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\t<NcActionLink\n\t\t\t\t\tv-if=\"link\"\n\t\t\t\t\t:href=\"link.href\"\n\t\t\t\t\t:target=\"link.target\"\n\t\t\t\t\t:title=\"resolveTitle(action)\"\n\t\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t\t:data-testid=\"`cn-action-item-${slugifyLabel(action.label)}`\"\n\t\t\t\t\tcloseAfterClick\n\t\t\t\t\t@click=\"onLinkAction(action, link, $event)\">\n\t\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcActionLink>\n\t\t\t\t<NcActionButton\n\t\t\t\t\tv-else\n\t\t\t\t\t:title=\"resolveTitle(action)\"\n\t\t\t\t\t:disabled=\"resolveDisabled(action)\"\n\t\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t\t:data-testid=\"`cn-action-item-${slugifyLabel(action.label)}`\"\n\t\t\t\t\tcloseAfterClick\n\t\t\t\t\t@click=\"onAction(action)\">\n\t\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcActionButton>\n\t\t\t</template>\n\n\t\t\t<!--\n\t\t\t@slot default\n\t\t\t@description Custom NcActionButton-family content rendered inside the\n\t\t\tNcActions menu. Use this for hardcoded buttons (Keepiq pattern) when\n\t\t\tthe `actions` prop is empty.\n\t\t-->\n\t\t\t<slot />\n\t\t</NcActions>\n\n\t\t<!-- CUSTOM PANEL — bypass NcActions, render arbitrary slot content\n\t\t anchored at the cursor. The `#panel:<name>` slot may contain any\n\t\t markup (grids, inputs, custom components) without the NcActions\n\t\t child-allowlist filter. -->\n\t\t<template v-if=\"activePanel && internalOpen\">\n\t\t\t<div\n\t\t\t\tclass=\"cn-context-menu__backdrop\"\n\t\t\t\t@click=\"onClose\"\n\t\t\t\t@contextmenu.prevent=\"onClose\" />\n\t\t\t<div\n\t\t\t\tref=\"panel\"\n\t\t\t\tclass=\"cn-context-menu__panel\"\n\t\t\t\trole=\"menu\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\tdata-testid=\"cn-context-menu-panel\"\n\t\t\t\t:data-panel=\"activePanel\"\n\t\t\t\t@keydown.esc.stop=\"onClose\"\n\t\t\t\t@click.stop>\n\t\t\t\t<!-- @slot panel:<name> Free-form custom panel content shown when\n\t\t\t\t `activePanel === '<name>'`. Bypasses the NcActions child\n\t\t\t\t allowlist — put any markup here (grids, inputs, custom\n\t\t\t\t components, etc.). The slot name is dynamic: define one\n\t\t\t\t `#panel:<name>` per panel you want to support. -->\n\t\t\t\t<!-- @binding {Function} back Clear `activePanel`, returning to the default action list. -->\n\t\t\t\t<!-- @binding {Function} close Close the entire menu, equivalent to clicking outside. -->\n\t\t\t\t<!-- @binding {*} targetItem The right-clicked item, forwarded from the `targetItem` prop. -->\n\t\t\t\t<slot\n\t\t\t\t\t:name=\"`panel:${activePanel}`\"\n\t\t\t\t\t:back=\"back\"\n\t\t\t\t\t:close=\"onClose\"\n\t\t\t\t\t:targetItem=\"targetItem\" />\n\t\t\t</div>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { NcActionButton, NcActionLink, NcActions } from '@nextcloud/vue'\nimport { CTX_MENU_DATA_ATTR, CTX_MENU_POPPER_ATTR } from '../../composables/useContextMenu.js'\nimport { followItemActionLink, resolveItemActionLink } from '../../utils/actionLink.js'\nimport { isModifiedClick } from '../../utils/linkNavigation.js'\nimport { CnIcon } from '../CnIcon/index.js'\n\n/**\n * CnContextMenu — Right-click context menu wrapper around NcActions.\n *\n * Provides a pre-configured NcActions instance that positions itself at the\n * cursor via the `useContextMenu` composable's CSS custom properties. Accepts\n * an `actions` array for the common dynamic case (like CnRowActions), and a\n * default slot for hardcoded NcActionButton content.\n *\n * Pair with `useContextMenu()` for state management (open/close, target item,\n * cursor positioning). The composable handles the DOM attributes; this component\n * handles the NcActions template boilerplate.\n *\n * Dynamic actions (CnIndexPage pattern)\n * ```vue\n * <CnContextMenu\n * v-model:open=\"contextMenuOpen\"\n * :actions=\"mergedActions\"\n * :target-item=\"contextMenuRow\"\n * @action=\"$emit('action', $event)\"\n * @close=\"closeContextMenu\" />\n * ```\n *\n * Custom buttons via slot (Keepiq pattern)\n * ```vue\n * <CnContextMenu\n * v-model:open=\"contextMenuOpen\"\n * @close=\"closeContextMenu\">\n * <NcActionButton close-after-click @click=\"onRename\">\n * <template #icon><PencilIcon :size=\"20\" /></template>\n * Rename\n * </NcActionButton>\n * </CnContextMenu>\n * ```\n */\nexport default {\n\tname: 'CnContextMenu',\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 * Whether the context menu is open. Bind with `v-model:open` — the `.sync`\n\t\t * modifier it replaces was removed in Vue 3 and binds nothing.\n\t\t * Bind to `useContextMenu().isOpen`.\n\t\t */\n\t\topen: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Action definitions rendered as NcActionButton items.\n\t\t * Same format as CnRowActions: `{ label, icon?, handler?, disabled?, visible?, title?, destructive? }`.\n\t\t * `icon` accepts either a component (rendered directly) or a string —\n\t\t * a string is treated as an MDI name and rendered via `CnIcon` (e.g.\n\t\t * `\"Eye\"`), which lets manifest (JSON) actions declare icons by name.\n\t\t * `visible` (boolean | (targetItem) => boolean) hides the entry when falsy\n\t\t * (default: shown). `title` (string | (targetItem) => string) renders as\n\t\t * a native tooltip — useful for explaining why an entry is disabled.\n\t\t * `href` (URL) or `to` (vue-router location), each a value or a\n\t\t * `(targetItem) => …` function, render the entry as a real link, with\n\t\t * `linkTarget` as its `target`; a link emits `action` but does not call\n\t\t * `handler`. When the entire array is empty (or all entries are filtered\n\t\t * out), only the default slot content is rendered.\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/**\n\t\t * The right-clicked item (row, folder, etc.). Passed to action `handler`\n\t\t * and `disabled` callbacks, and included in the `action` event payload.\n\t\t * Bind to `useContextMenu().targetItem`.\n\t\t */\n\t\ttargetItem: {\n\t\t\ttype: [Object, String, Number],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Name of the currently-active custom panel. When set (and the menu is\n\t\t * open), the default NcActions list is replaced by the matching\n\t\t * `#panel:<name>` slot, rendered free-form (no NcActions child filter).\n\t\t * Bind with `v-model:active-panel` — the component emits `update:activePanel(null)` when\n\t\t * the panel's `back()` binding is invoked or the menu closes.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tactivePanel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['action', 'close', 'closed', 'update:activePanel', 'update:open'],\n\n\tdata() {\n\t\treturn {\n\t\t\tinternalOpen: this.open,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Filter actions by their `visible` predicate. Entries without\n\t\t * `visible` are always shown (backwards compatible).\n\t\t *\n\t\t * @return {Array} Visible actions for the current targetItem.\n\t\t */\n\t\tvisibleActions() {\n\t\t\treturn this.actions.filter((action) => {\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.targetItem)\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.resolveDisabled(action) ? null : resolveItemActionLink(action, this.targetItem, this.$router),\n\t\t\t}))\n\t\t},\n\t},\n\n\twatch: {\n\t\topen(val) {\n\t\t\tthis.internalOpen = val\n\t\t},\n\n\t\tinternalOpen(val) {\n\t\t\t// Description goes ABOVE `@event`, not inline after it:\n\t\t\t// vue-docgen-api's event-name splitter stops at the first `:`, so\n\t\t\t// `@event update:open <description>` is read as one long event NAME\n\t\t\t// and the generated docs show an empty description.\n\t\t\t/**\n\t\t\t * Fired whenever the menu's internal open state flips. Used by\n\t\t\t * callers that bind `v-model:open` to track visibility.\n\t\t\t *\n\t\t\t * @event update:open\n\t\t\t * @type {boolean}\n\t\t\t */\n\t\t\tthis.$emit('update:open', val)\n\n\t\t\tif (val) {\n\t\t\t\tthis.tagPopper()\n\t\t\t\tthis.startOutsideWatch()\n\t\t\t} else {\n\t\t\t\tthis.stopOutsideWatch()\n\t\t\t\t// NcActions' `@closed` never fires (see `onClosed`), so drop the\n\t\t\t\t// \"a context menu is open\" attribute here. The CSS vars stay put\n\t\t\t\t// — the hide animation still needs the transform, and the next\n\t\t\t\t// `open()` overwrites them before the popper shows again.\n\t\t\t\tdocument.documentElement.removeAttribute(CTX_MENU_DATA_ATTR)\n\t\t\t}\n\t\t},\n\t},\n\n\tmounted() {\n\t\t// NcActions renders its own NcButton as the popover trigger; the menu\n\t\t// opens only via right-click (via `:open.sync`), so the button is\n\t\t// offscreen-positioned (see the `.cn-context-menu` rule below).\n\t\t//\n\t\t// Use `inert` rather than `aria-hidden`: when the menu is dismissed\n\t\t// without picking an item, NcPopover's focus-trap restores focus to the\n\t\t// trigger button. With `aria-hidden` that produces a \"Blocked\n\t\t// aria-hidden on an element because its descendant retained focus\"\n\t\t// warning (focused element must not be hidden from AT). `inert` both\n\t\t// hides the button from the accessibility tree *and* makes it\n\t\t// unfocusable, so the trap's focus restore falls back to <body> instead\n\t\t// of landing on a hidden element. (The button never needs focus — the\n\t\t// menu opens via `:open.sync`, not by focusing the trigger.)\n\t\tconst trigger = this.$el?.querySelector('.action-item__menutoggle')\n\t\tif (trigger) {\n\t\t\ttrigger.setAttribute('inert', '')\n\t\t}\n\n\t\t// Silence NcPopover's dev-mode a11y warning for this specific instance.\n\t\t//\n\t\t// NcPopover (when `window.OC?.debug` is on) calls\n\t\t// `tabbable(triggerContainer)[0]` on every open and warns\n\t\t// \"It looks like you are using a custom button as a <NcPopover>\n\t\t// trigger…\" if nothing tabbable is found. We *are* using NcButton —\n\t\t// the warning's escape hatch — but the button is offscreen-positioned\n\t\t// and tabbable's visibility checks won't reliably find it across\n\t\t// browsers (Chromium's `Element.checkVisibility()` excludes some\n\t\t// offscreen / clipped elements). The advice in the warning (\"bind\n\t\t// #trigger slot attrs\") doesn't apply: NcActions controls its own\n\t\t// trigger and doesn't expose a `#trigger` slot to override.\n\t\t//\n\t\t// Neutralising the check on our own NcPopover instance (one level\n\t\t// down inside NcActions) leaves every other popover untouched and\n\t\t// keeps the check live for real custom triggers elsewhere.\n\t\t//\n\t\t// Vue 3: `this.$children` was removed (it hard-errors under @vue/compat\n\t\t// MODE 3 with \"INSTANCE_CHILDREN compat has been disabled\"), so reach the\n\t\t// NcActions instance through an explicit template ref instead.\n\t\tconst ncActions = this.$refs.actions\n\t\tconst ncPopover = ncActions?.$refs?.popover\n\t\tif (ncPopover) {\n\t\t\tncPopover.checkTriggerA11y = () => {}\n\t\t}\n\n\t\tthis.tagPopper()\n\n\t\t// Mounting with `open` already true never trips the watcher.\n\t\tif (this.internalOpen) {\n\t\t\tthis.startOutsideWatch()\n\t\t}\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.stopOutsideWatch()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Mark this menu's own popper element so the cursor-positioning CSS in\n\t\t * `src/css/context-menu.css` can target it — and nothing else.\n\t\t *\n\t\t * That CSS used to key on the `<html>` data attribute alone, which\n\t\t * matches *every* `.v-popper__popper` in the document: any other popover\n\t\t * alive while a context menu was open (a table row's actions menu, most\n\t\t * visibly) got dragged to the last right-click coordinates.\n\t\t *\n\t\t * The marker has to be applied imperatively — NcActions hardcodes\n\t\t * `popoverBaseClass: 'action-item__popper'` on its NcPopover and spreads\n\t\t * no `$attrs` onto it, so there is no prop to pass one in through.\n\t\t *\n\t\t * It must be an *attribute*, not a class. floating-vue binds a dynamic\n\t\t * `class` on this element (`--shown` / `--hidden` / `--show-from` / …)\n\t\t * and Vue's `patchClass` assigns `el.className` wholesale, so a class\n\t\t * added here is wiped the first time the popper opens — the rule then\n\t\t * matches nothing and the menu renders at its offscreen trigger. Vue\n\t\t * only patches props present in the vnode, so an attribute it never\n\t\t * renders is left alone.\n\t\t *\n\t\t * The element itself is stable: floating-vue keeps one `$_popperNode`\n\t\t * and `appendChild`s it into the container on first show, so marking it\n\t\t * once at `mounted()` holds for the component's lifetime. Re-running on\n\t\t * open is a cheap, idempotent guard for a not-yet-ready ref chain.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\ttagPopper() {\n\t\t\tconst el = this.$refs.actions?.$refs?.popover?.getPopoverContentElement?.()\n\t\t\tel?.setAttribute?.(CTX_MENU_POPPER_ATTR, '')\n\t\t},\n\n\t\t/**\n\t\t * Start watching for a press outside the menu. Driven from both the\n\t\t * `internalOpen` watcher and `mounted()`, because a consumer may mount\n\t\t * with `open` already true — in which case there is no state transition\n\t\t * for the watcher to see.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartOutsideWatch() {\n\t\t\t// nextTick: the contextmenu/click that opened the menu is still\n\t\t\t// propagating and would dismiss it again on the same gesture.\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tif (this.internalOpen) {\n\t\t\t\t\t// CAPTURE. A canvas that swallows its own mousedown never lets it\n\t\t\t\t\t// bubble to document, so a menu opened over one could not be\n\t\t\t\t\t// dismissed by clicking the canvas — Vue Flow's d3-zoom pane calls\n\t\t\t\t\t// stopImmediatePropagation, which is exactly that shape.\n\t\t\t\t\tdocument.addEventListener('mousedown', this.onDocumentMouseDown, true)\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Stop watching for outside presses.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstopOutsideWatch() {\n\t\t\tdocument.removeEventListener('mousedown', this.onDocumentMouseDown, true)\n\t\t},\n\n\t\t/**\n\t\t * Close the menu when the press lands outside it.\n\t\t *\n\t\t * NcActions has to run in `manual-open` mode here (the menu is opened\n\t\t * from a right-click via the `open` prop, not by activating the trigger\n\t\t * button). `@nextcloud/vue` 9 couples that mode to\n\t\t * `noCloseOnClickOutside: this.manualOpen`, and NcPopover derives\n\t\t * `autoHide: !noCloseOnClickOutside && closeOnClickOutside` — so the\n\t\t * popper's own outside-click dismissal is switched off and nothing else\n\t\t * closes the menu. v8 only cleared the popper's open `triggers`, leaving\n\t\t * autoHide intact, which is why dismissing worked before the Vue 3\n\t\t * upgrade. NcActions spreads no `$attrs` onto NcPopover, so the prop\n\t\t * cannot be overridden from outside — hence handling it here.\n\t\t *\n\t\t * @param {MouseEvent} event The document mousedown.\n\t\t * @return {void}\n\t\t */\n\t\tonDocumentMouseDown(event) {\n\t\t\tif (!this.internalOpen || this.isInsideMenu(event.target)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.onClose()\n\t\t},\n\n\t\t/**\n\t\t * Whether a node sits inside the menu's own surface — the custom panel,\n\t\t * or the popper NcActions teleports to `container=\"body\"` (so it is not\n\t\t * inside `this.$el` and `contains()` on the root would miss it).\n\t\t *\n\t\t * @param {EventTarget} target The event target to test.\n\t\t * @return {boolean} True when the target belongs to this menu.\n\t\t */\n\t\tisInsideMenu(target) {\n\t\t\tif (!(target instanceof Node)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (this.$refs.panel?.contains(target)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst content = this.$refs.actions?.$refs?.popover?.getPopoverContentElement?.()\n\t\t\tif (content?.contains(target)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\t// Fallback for when the popover ref chain is unavailable —\n\t\t\t// `action-item__popper` is the base class NcActions gives its popper.\n\t\t\treturn target instanceof Element && !!target.closest('.action-item__popper')\n\t\t},\n\n\t\tresolveDisabled(action) {\n\t\t\tif (typeof action.disabled === 'function') {\n\t\t\t\treturn action.disabled(this.targetItem)\n\t\t\t}\n\t\t\treturn !!action.disabled\n\t\t},\n\n\t\t/**\n\t\t * Resolve the `title` field on an action descriptor — supports both\n\t\t * a static string and a function `(targetItem) => string`. Returns\n\t\t * undefined when no title is provided so the attribute isn't rendered.\n\t\t *\n\t\t * @param {object} action The action descriptor.\n\t\t * @return {string|undefined} The tooltip text, or undefined.\n\t\t */\n\t\tresolveTitle(action) {\n\t\t\tif (typeof action.title === 'function') {\n\t\t\t\treturn action.title(this.targetItem) || undefined\n\t\t\t}\n\t\t\treturn action.title || undefined\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\n\t\tonAction(action) {\n\t\t\tif (action.handler && typeof action.handler === 'function') {\n\t\t\t\taction.handler(this.targetItem)\n\t\t\t}\n\t\t\t/**\n\t\t\t * @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.\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.targetItem })\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 descriptor.\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\tthis.$emit('action', { action: action.label, row: this.targetItem })\n\t\t},\n\n\t\tonClose() {\n\t\t\tthis.internalOpen = false\n\t\t\tif (this.activePanel) {\n\t\t\t\tthis.$emit('update:activePanel', null)\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event close Fired when the menu starts closing (before the popper's hide animation). This is the reliable one — prefer it over `closed`.\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\n\t\t/**\n\t\t * Clear the active panel and return to the default action list.\n\t\t * Exposed to custom panel slots via the `back` scope binding.\n\t\t */\n\t\tback() {\n\t\t\t// Description above the tag — see the note on `update:open`.\n\t\t\t/**\n\t\t\t * Emitted with `null` to clear the active panel — fired by the\n\t\t\t * panel's `back()` binding and whenever the menu closes while a\n\t\t\t * panel is open. Bind with `v-model:active-panel`.\n\t\t\t *\n\t\t\t * @event update:activePanel\n\t\t\t * @type {null}\n\t\t\t */\n\t\t\tthis.$emit('update:activePanel', null)\n\t\t},\n\n\t\t/**\n\t\t * Fires after the popper's hide animation completes.\n\t\t *\n\t\t * IT MUST NOT CLEAR THE CURSOR POSITION, and it used to. This fires LATE\n\t\t * — a whole hide animation late — so a second click on the same target\n\t\t * reopens the menu before it arrives, and the tear-down then strips the\n\t\t * coordinates that reopen had just written. The transform resolves\n\t\t * against undefined vars and the menu lands in the corner, on every open\n\t\t * after the first.\n\t\t *\n\t\t * It was dead code when written: `@nextcloud/vue` 9 bound `onAfterClose`\n\t\t * against an NcPopover that only emitted `afterHide`. 9.11.0 binds\n\t\t * `onAfterHide`, so it runs now, and a tear-down nothing needed became a\n\t\t * bug. Nothing needs it still: positioning is scoped to this menu's own\n\t\t * popper (see `tagPopper`), the data attribute is dropped in the\n\t\t * `internalOpen` watcher, the next `open()` overwrites the vars, and the\n\t\t * composable's unmount hook clears them for good.\n\t\t */\n\t\tonClosed() {\n\t\t\t/**\n\t\t\t * @event closed Intended to fire after the popper's hide animation completes. Does not currently fire — `@nextcloud/vue` 9's NcActions listens for an `afterClose` event NcPopover never emits. Use `close` instead.\n\t\t\t */\n\t\t\tthis.$emit('closed')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* Selector is deliberately two classes deep. `.cn-context-menu` lands on\n NcActions' root, which NcActions itself styles as `.action-item[data-v-…]\n { position: relative; display: inline-block }`. Scoped CSS appends the attr\n to the last compound only, so a bare `.cn-context-menu` tied that rule at\n specificity (0,2,0) and lost on injection order — the trigger stayed\n `position: relative`, so it kept its in-flow box (top/left just offset it\n visually) and showed up as a full-width ~34px-tall phantom row between the\n table and the pagination on CnIndexPage. Adding the parent class makes this\n (0,3,0) so it wins regardless of order. */\n.cn-context-menu-root > .cn-context-menu {\n\t/* Hide the NcActions trigger button — menu opens only via right-click.\n\t Off-screen rather than display:none / visibility:hidden so NcPopover's\n\t a11y check (`tabbable(triggerContainer)[0]`) still finds the NcButton\n\t and the \"custom button as a <NcPopover> trigger\" warning stays quiet.\n\t Avoid `clip` / `clip-path` here — modern browsers' `Element.checkVisibility()`,\n\t which tabbable calls into, treats those as invisible. Pure offscreen\n\t positioning leaves the element fully \"visible\" to layout while being\n\t off the user's screen. */\n\tposition: absolute;\n\ttop: -9999px;\n\tleft: -9999px;\n\topacity: 0;\n\tpointer-events: none;\n}\n\n.cn-context-menu__backdrop {\n\tposition: fixed;\n\tinset: 0;\n\tz-index: 9998;\n}\n\n.cn-context-menu__panel {\n\tposition: fixed;\n\ttop: var(--cn-ctx-menu-y, 0);\n\tleft: var(--cn-ctx-menu-x, 0);\n\tz-index: 9999;\n\tmin-width: 220px;\n\tpadding: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 12px);\n\tbox-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);\n\toutline: none;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createBlock","_Fragment","_renderList","_normalizeClass","_toDisplayString","_resolveDynamicComponent","_renderSlot","_createElementVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnContextMenu.vue3.js","sources":["../../../../src/components/CnContextMenu/CnContextMenu.vue?vue&type=template&id=1bef81d7&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-context-menu-root\">\n\t\t<NcActions\n\t\t\tv-if=\"!activePanel\"\n\t\t\tref=\"actions\"\n\t\t\tv-model:open=\"internalOpen\"\n\t\t\t:manualOpen=\"true\"\n\t\t\t:forceMenu=\"true\"\n\t\t\tclass=\"cn-context-menu\"\n\t\t\tcontainer=\"body\"\n\t\t\tdata-testid=\"cn-context-menu\"\n\t\t\t@close=\"onClose\"\n\t\t\t@closed=\"onClosed\">\n\t\t\t<!-- Dynamic actions from array prop -->\n\t\t\t<template v-for=\"{ action, link } in renderedActions\" :key=\"actionKey(action)\">\n\t\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\t<NcActionLink\n\t\t\t\t\tv-if=\"link\"\n\t\t\t\t\t:href=\"link.href\"\n\t\t\t\t\t:target=\"link.target\"\n\t\t\t\t\t:title=\"resolveTitle(action)\"\n\t\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t\t:data-testid=\"actionTestId(action)\"\n\t\t\t\t\tcloseAfterClick\n\t\t\t\t\t@click=\"onLinkAction(action, link, $event)\">\n\t\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcActionLink>\n\t\t\t\t<NcActionButton\n\t\t\t\t\tv-else\n\t\t\t\t\t:title=\"resolveTitle(action)\"\n\t\t\t\t\t:disabled=\"resolveDisabled(action)\"\n\t\t\t\t\t:class=\"{ 'cn-row-action--destructive': action.destructive }\"\n\t\t\t\t\t:data-testid=\"actionTestId(action)\"\n\t\t\t\t\tcloseAfterClick\n\t\t\t\t\t@click=\"onAction(action)\">\n\t\t\t\t\t<template v-if=\"action.icon\" #icon>\n\t\t\t\t\t\t<CnIcon v-if=\"typeof action.icon === 'string'\" :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t\t<component :is=\"action.icon\" v-else :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ action.label }}\n\t\t\t\t</NcActionButton>\n\t\t\t</template>\n\n\t\t\t<!--\n\t\t\t@slot default\n\t\t\t@description Custom NcActionButton-family content rendered inside the\n\t\t\tNcActions menu. Use this for hardcoded buttons (Keepiq pattern) when\n\t\t\tthe `actions` prop is empty.\n\t\t-->\n\t\t\t<slot />\n\t\t</NcActions>\n\n\t\t<!-- CUSTOM PANEL — bypass NcActions, render arbitrary slot content\n\t\t anchored at the cursor. The `#panel:<name>` slot may contain any\n\t\t markup (grids, inputs, custom components) without the NcActions\n\t\t child-allowlist filter. -->\n\t\t<template v-if=\"activePanel && internalOpen\">\n\t\t\t<div\n\t\t\t\tclass=\"cn-context-menu__backdrop\"\n\t\t\t\t@click=\"onClose\"\n\t\t\t\t@contextmenu.prevent=\"onClose\" />\n\t\t\t<div\n\t\t\t\tref=\"panel\"\n\t\t\t\tclass=\"cn-context-menu__panel\"\n\t\t\t\trole=\"menu\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\tdata-testid=\"cn-context-menu-panel\"\n\t\t\t\t:data-panel=\"activePanel\"\n\t\t\t\t@keydown.esc.stop=\"onClose\"\n\t\t\t\t@click.stop>\n\t\t\t\t<!-- @slot panel:<name> Free-form custom panel content shown when\n\t\t\t\t `activePanel === '<name>'`. Bypasses the NcActions child\n\t\t\t\t allowlist — put any markup here (grids, inputs, custom\n\t\t\t\t components, etc.). The slot name is dynamic: define one\n\t\t\t\t `#panel:<name>` per panel you want to support. -->\n\t\t\t\t<!-- @binding {Function} back Clear `activePanel`, returning to the default action list. -->\n\t\t\t\t<!-- @binding {Function} close Close the entire menu, equivalent to clicking outside. -->\n\t\t\t\t<!-- @binding {*} targetItem The right-clicked item, forwarded from the `targetItem` prop. -->\n\t\t\t\t<slot\n\t\t\t\t\t:name=\"`panel:${activePanel}`\"\n\t\t\t\t\t:back=\"back\"\n\t\t\t\t\t:close=\"onClose\"\n\t\t\t\t\t:targetItem=\"targetItem\" />\n\t\t\t</div>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { NcActionButton, NcActionLink, NcActions } from '@nextcloud/vue'\nimport { CTX_MENU_DATA_ATTR, CTX_MENU_POPPER_ATTR } from '../../composables/useContextMenu.js'\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 * CnContextMenu — Right-click context menu wrapper around NcActions.\n *\n * Provides a pre-configured NcActions instance that positions itself at the\n * cursor via the `useContextMenu` composable's CSS custom properties. Accepts\n * an `actions` array for the common dynamic case (like CnRowActions), and a\n * default slot for hardcoded NcActionButton content.\n *\n * Pair with `useContextMenu()` for state management (open/close, target item,\n * cursor positioning). The composable handles the DOM attributes; this component\n * handles the NcActions template boilerplate.\n *\n * Dynamic actions (CnIndexPage pattern).\n * CnIndexPage passes the same per-row list its row actions menu renders, so the two menus always match.\n * ```vue\n * <CnContextMenu\n * v-model:open=\"contextMenuOpen\"\n * :actions=\"rowActionsFor(contextMenuShownRow)\"\n * :target-item=\"contextMenuShownRow\"\n * @action=\"$emit('action', $event)\"\n * @close=\"closeContextMenu\" />\n * ```\n *\n * Custom buttons via slot (Keepiq pattern)\n * ```vue\n * <CnContextMenu\n * v-model:open=\"contextMenuOpen\"\n * @close=\"closeContextMenu\">\n * <NcActionButton close-after-click @click=\"onRename\">\n * <template #icon><PencilIcon :size=\"20\" /></template>\n * Rename\n * </NcActionButton>\n * </CnContextMenu>\n * ```\n */\nexport default {\n\tname: 'CnContextMenu',\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 * Whether the context menu is open. Bind with `v-model:open` — the `.sync`\n\t\t * modifier it replaces was removed in Vue 3 and binds nothing.\n\t\t * Bind to `useContextMenu().isOpen`.\n\t\t */\n\t\topen: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Action definitions rendered as NcActionButton items.\n\t\t * Same format as CnRowActions: `{ label, icon?, handler?, disabled?, visible?, title?, destructive? }`.\n\t\t * `icon` accepts either a component (rendered directly) or a string —\n\t\t * a string is treated as an MDI name and rendered via `CnIcon` (e.g.\n\t\t * `\"Eye\"`), which lets manifest (JSON) actions declare icons by name.\n\t\t * `visible` (boolean | (targetItem) => boolean) hides the entry when falsy\n\t\t * (default: shown). `title` (string | (targetItem) => string) renders as\n\t\t * a native tooltip — useful for explaining why an entry is disabled.\n\t\t * A locally decidable `visibleWhen` that is false for the target hides the entry too, as in CnRowActions.\n\t\t * `id` is passed in the `action` payload; `builtin` marks a CnIndexPage built-in, whose testid is `cn-action-item-<id>` in every locale.\n\t\t * `href` (URL) or `to` (vue-router location), each a value or a\n\t\t * `(targetItem) => …` function, render the entry as a real link, with\n\t\t * `linkTarget` as its `target`; a link emits `action` but does not call\n\t\t * `handler`. When the entire array is empty (or all entries are filtered\n\t\t * out), only the default slot content is rendered.\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), visibleWhen: object, 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/**\n\t\t * The right-clicked item (row, folder, etc.). Passed to action `handler`\n\t\t * and `disabled` callbacks, and included in the `action` event payload.\n\t\t * Bind to `useContextMenu().targetItem`.\n\t\t */\n\t\ttargetItem: {\n\t\t\ttype: [Object, String, Number],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Name of the currently-active custom panel. When set (and the menu is\n\t\t * open), the default NcActions list is replaced by the matching\n\t\t * `#panel:<name>` slot, rendered free-form (no NcActions child filter).\n\t\t * Bind with `v-model:active-panel` — the component emits `update:activePanel(null)` when\n\t\t * the panel's `back()` binding is invoked or the menu closes.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tactivePanel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['action', 'close', 'closed', 'update:activePanel', 'update:open'],\n\n\tdata() {\n\t\treturn {\n\t\t\tinternalOpen: this.open,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Filter actions by their `visibleWhen` and `visible` gates, with the same rule CnRowActions uses.\n\t\t * Entries without either always show.\n\t\t *\n\t\t * @return {Array} Visible actions for the current targetItem.\n\t\t */\n\t\tvisibleActions() {\n\t\t\treturn this.actions.filter((action) => isRowActionVisible(action, this.targetItem))\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.resolveDisabled(action) ? null : resolveItemActionLink(action, this.targetItem, this.$router),\n\t\t\t}))\n\t\t},\n\t},\n\n\twatch: {\n\t\topen(val) {\n\t\t\tthis.internalOpen = val\n\t\t},\n\n\t\tinternalOpen(val) {\n\t\t\t// Description goes ABOVE `@event`, not inline after it:\n\t\t\t// vue-docgen-api's event-name splitter stops at the first `:`, so\n\t\t\t// `@event update:open <description>` is read as one long event NAME\n\t\t\t// and the generated docs show an empty description.\n\t\t\t/**\n\t\t\t * Fired whenever the menu's internal open state flips. Used by\n\t\t\t * callers that bind `v-model:open` to track visibility.\n\t\t\t *\n\t\t\t * @event update:open\n\t\t\t * @type {boolean}\n\t\t\t */\n\t\t\tthis.$emit('update:open', val)\n\n\t\t\tif (val) {\n\t\t\t\tthis.tagPopper()\n\t\t\t\tthis.startOutsideWatch()\n\t\t\t} else {\n\t\t\t\tthis.stopOutsideWatch()\n\t\t\t\t// NcActions' `@closed` never fires (see `onClosed`), so drop the\n\t\t\t\t// \"a context menu is open\" attribute here. The CSS vars stay put\n\t\t\t\t// — the hide animation still needs the transform, and the next\n\t\t\t\t// `open()` overwrites them before the popper shows again.\n\t\t\t\tdocument.documentElement.removeAttribute(CTX_MENU_DATA_ATTR)\n\t\t\t}\n\t\t},\n\t},\n\n\tmounted() {\n\t\t// NcActions renders its own NcButton as the popover trigger; the menu\n\t\t// opens only via right-click (via `:open.sync`), so the button is\n\t\t// offscreen-positioned (see the `.cn-context-menu` rule below).\n\t\t//\n\t\t// Use `inert` rather than `aria-hidden`: when the menu is dismissed\n\t\t// without picking an item, NcPopover's focus-trap restores focus to the\n\t\t// trigger button. With `aria-hidden` that produces a \"Blocked\n\t\t// aria-hidden on an element because its descendant retained focus\"\n\t\t// warning (focused element must not be hidden from AT). `inert` both\n\t\t// hides the button from the accessibility tree *and* makes it\n\t\t// unfocusable, so the trap's focus restore falls back to <body> instead\n\t\t// of landing on a hidden element. (The button never needs focus — the\n\t\t// menu opens via `:open.sync`, not by focusing the trigger.)\n\t\tconst trigger = this.$el?.querySelector('.action-item__menutoggle')\n\t\tif (trigger) {\n\t\t\ttrigger.setAttribute('inert', '')\n\t\t}\n\n\t\t// Silence NcPopover's dev-mode a11y warning for this specific instance.\n\t\t//\n\t\t// NcPopover (when `window.OC?.debug` is on) calls\n\t\t// `tabbable(triggerContainer)[0]` on every open and warns\n\t\t// \"It looks like you are using a custom button as a <NcPopover>\n\t\t// trigger…\" if nothing tabbable is found. We *are* using NcButton —\n\t\t// the warning's escape hatch — but the button is offscreen-positioned\n\t\t// and tabbable's visibility checks won't reliably find it across\n\t\t// browsers (Chromium's `Element.checkVisibility()` excludes some\n\t\t// offscreen / clipped elements). The advice in the warning (\"bind\n\t\t// #trigger slot attrs\") doesn't apply: NcActions controls its own\n\t\t// trigger and doesn't expose a `#trigger` slot to override.\n\t\t//\n\t\t// Neutralising the check on our own NcPopover instance (one level\n\t\t// down inside NcActions) leaves every other popover untouched and\n\t\t// keeps the check live for real custom triggers elsewhere.\n\t\t//\n\t\t// Vue 3: `this.$children` was removed (it hard-errors under @vue/compat\n\t\t// MODE 3 with \"INSTANCE_CHILDREN compat has been disabled\"), so reach the\n\t\t// NcActions instance through an explicit template ref instead.\n\t\tconst ncActions = this.$refs.actions\n\t\tconst ncPopover = ncActions?.$refs?.popover\n\t\tif (ncPopover) {\n\t\t\tncPopover.checkTriggerA11y = () => {}\n\t\t}\n\n\t\tthis.tagPopper()\n\n\t\t// Mounting with `open` already true never trips the watcher.\n\t\tif (this.internalOpen) {\n\t\t\tthis.startOutsideWatch()\n\t\t}\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.stopOutsideWatch()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Mark this menu's own popper element so the cursor-positioning CSS in\n\t\t * `src/css/context-menu.css` can target it — and nothing else.\n\t\t *\n\t\t * That CSS used to key on the `<html>` data attribute alone, which\n\t\t * matches *every* `.v-popper__popper` in the document: any other popover\n\t\t * alive while a context menu was open (a table row's actions menu, most\n\t\t * visibly) got dragged to the last right-click coordinates.\n\t\t *\n\t\t * The marker has to be applied imperatively — NcActions hardcodes\n\t\t * `popoverBaseClass: 'action-item__popper'` on its NcPopover and spreads\n\t\t * no `$attrs` onto it, so there is no prop to pass one in through.\n\t\t *\n\t\t * It must be an *attribute*, not a class. floating-vue binds a dynamic\n\t\t * `class` on this element (`--shown` / `--hidden` / `--show-from` / …)\n\t\t * and Vue's `patchClass` assigns `el.className` wholesale, so a class\n\t\t * added here is wiped the first time the popper opens — the rule then\n\t\t * matches nothing and the menu renders at its offscreen trigger. Vue\n\t\t * only patches props present in the vnode, so an attribute it never\n\t\t * renders is left alone.\n\t\t *\n\t\t * The element itself is stable: floating-vue keeps one `$_popperNode`\n\t\t * and `appendChild`s it into the container on first show, so marking it\n\t\t * once at `mounted()` holds for the component's lifetime. Re-running on\n\t\t * open is a cheap, idempotent guard for a not-yet-ready ref chain.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\ttagPopper() {\n\t\t\tconst el = this.$refs.actions?.$refs?.popover?.getPopoverContentElement?.()\n\t\t\tel?.setAttribute?.(CTX_MENU_POPPER_ATTR, '')\n\t\t},\n\n\t\t/**\n\t\t * Start watching for a press outside the menu. Driven from both the\n\t\t * `internalOpen` watcher and `mounted()`, because a consumer may mount\n\t\t * with `open` already true — in which case there is no state transition\n\t\t * for the watcher to see.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartOutsideWatch() {\n\t\t\t// nextTick: the contextmenu/click that opened the menu is still\n\t\t\t// propagating and would dismiss it again on the same gesture.\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tif (this.internalOpen) {\n\t\t\t\t\t// CAPTURE. A canvas that swallows its own mousedown never lets it\n\t\t\t\t\t// bubble to document, so a menu opened over one could not be\n\t\t\t\t\t// dismissed by clicking the canvas — Vue Flow's d3-zoom pane calls\n\t\t\t\t\t// stopImmediatePropagation, which is exactly that shape.\n\t\t\t\t\tdocument.addEventListener('mousedown', this.onDocumentMouseDown, true)\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Stop watching for outside presses.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstopOutsideWatch() {\n\t\t\tdocument.removeEventListener('mousedown', this.onDocumentMouseDown, true)\n\t\t},\n\n\t\t/**\n\t\t * Close the menu when the press lands outside it.\n\t\t *\n\t\t * NcActions has to run in `manual-open` mode here (the menu is opened\n\t\t * from a right-click via the `open` prop, not by activating the trigger\n\t\t * button). `@nextcloud/vue` 9 couples that mode to\n\t\t * `noCloseOnClickOutside: this.manualOpen`, and NcPopover derives\n\t\t * `autoHide: !noCloseOnClickOutside && closeOnClickOutside` — so the\n\t\t * popper's own outside-click dismissal is switched off and nothing else\n\t\t * closes the menu. v8 only cleared the popper's open `triggers`, leaving\n\t\t * autoHide intact, which is why dismissing worked before the Vue 3\n\t\t * upgrade. NcActions spreads no `$attrs` onto NcPopover, so the prop\n\t\t * cannot be overridden from outside — hence handling it here.\n\t\t *\n\t\t * @param {MouseEvent} event The document mousedown.\n\t\t * @return {void}\n\t\t */\n\t\tonDocumentMouseDown(event) {\n\t\t\tif (!this.internalOpen || this.isInsideMenu(event.target)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.onClose()\n\t\t},\n\n\t\t/**\n\t\t * Whether a node sits inside the menu's own surface — the custom panel,\n\t\t * or the popper NcActions teleports to `container=\"body\"` (so it is not\n\t\t * inside `this.$el` and `contains()` on the root would miss it).\n\t\t *\n\t\t * @param {EventTarget} target The event target to test.\n\t\t * @return {boolean} True when the target belongs to this menu.\n\t\t */\n\t\tisInsideMenu(target) {\n\t\t\tif (!(target instanceof Node)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (this.$refs.panel?.contains(target)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst content = this.$refs.actions?.$refs?.popover?.getPopoverContentElement?.()\n\t\t\tif (content?.contains(target)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\t// Fallback for when the popover ref chain is unavailable —\n\t\t\t// `action-item__popper` is the base class NcActions gives its popper.\n\t\t\treturn target instanceof Element && !!target.closest('.action-item__popper')\n\t\t},\n\n\t\tresolveDisabled(action) {\n\t\t\tif (typeof action.disabled === 'function') {\n\t\t\t\treturn action.disabled(this.targetItem)\n\t\t\t}\n\t\t\treturn !!action.disabled\n\t\t},\n\n\t\t/**\n\t\t * Resolve the `title` field on an action descriptor — supports both\n\t\t * a static string and a function `(targetItem) => string`. Returns\n\t\t * undefined when no title is provided so the attribute isn't rendered.\n\t\t *\n\t\t * @param {object} action The action descriptor.\n\t\t * @return {string|undefined} The tooltip text, or undefined.\n\t\t */\n\t\tresolveTitle(action) {\n\t\t\tif (typeof action.title === 'function') {\n\t\t\t\treturn action.title(this.targetItem) || undefined\n\t\t\t}\n\t\t\treturn action.title || undefined\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 descriptor.\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 descriptor.\n\t\t * @return {string} The key.\n\t\t */\n\t\tactionKey(action) {\n\t\t\treturn rowActionKey(action)\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.targetItem)\n\t\t\t}\n\t\t\t/**\n\t\t\t * @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.\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.targetItem))\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 descriptor.\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\tthis.$emit('action', rowActionPayload(action, this.targetItem))\n\t\t},\n\n\t\tonClose() {\n\t\t\tthis.internalOpen = false\n\t\t\tif (this.activePanel) {\n\t\t\t\tthis.$emit('update:activePanel', null)\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event close Fired when the menu starts closing (before the popper's hide animation). This is the reliable one — prefer it over `closed`.\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\n\t\t/**\n\t\t * Clear the active panel and return to the default action list.\n\t\t * Exposed to custom panel slots via the `back` scope binding.\n\t\t */\n\t\tback() {\n\t\t\t// Description above the tag — see the note on `update:open`.\n\t\t\t/**\n\t\t\t * Emitted with `null` to clear the active panel — fired by the\n\t\t\t * panel's `back()` binding and whenever the menu closes while a\n\t\t\t * panel is open. Bind with `v-model:active-panel`.\n\t\t\t *\n\t\t\t * @event update:activePanel\n\t\t\t * @type {null}\n\t\t\t */\n\t\t\tthis.$emit('update:activePanel', null)\n\t\t},\n\n\t\t/**\n\t\t * Fires after the popper's hide animation completes.\n\t\t *\n\t\t * IT MUST NOT CLEAR THE CURSOR POSITION, and it used to. This fires LATE\n\t\t * — a whole hide animation late — so a second click on the same target\n\t\t * reopens the menu before it arrives, and the tear-down then strips the\n\t\t * coordinates that reopen had just written. The transform resolves\n\t\t * against undefined vars and the menu lands in the corner, on every open\n\t\t * after the first.\n\t\t *\n\t\t * It was dead code when written: `@nextcloud/vue` 9 bound `onAfterClose`\n\t\t * against an NcPopover that only emitted `afterHide`. 9.11.0 binds\n\t\t * `onAfterHide`, so it runs now, and a tear-down nothing needed became a\n\t\t * bug. Nothing needs it still: positioning is scoped to this menu's own\n\t\t * popper (see `tagPopper`), the data attribute is dropped in the\n\t\t * `internalOpen` watcher, the next `open()` overwrites the vars, and the\n\t\t * composable's unmount hook clears them for good.\n\t\t */\n\t\tonClosed() {\n\t\t\t/**\n\t\t\t * @event closed Intended to fire after the popper's hide animation completes. Does not currently fire — `@nextcloud/vue` 9's NcActions listens for an `afterClose` event NcPopover never emits. Use `close` instead.\n\t\t\t */\n\t\t\tthis.$emit('closed')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* Selector is deliberately two classes deep. `.cn-context-menu` lands on\n NcActions' root, which NcActions itself styles as `.action-item[data-v-…]\n { position: relative; display: inline-block }`. Scoped CSS appends the attr\n to the last compound only, so a bare `.cn-context-menu` tied that rule at\n specificity (0,2,0) and lost on injection order — the trigger stayed\n `position: relative`, so it kept its in-flow box (top/left just offset it\n visually) and showed up as a full-width ~34px-tall phantom row between the\n table and the pagination on CnIndexPage. Adding the parent class makes this\n (0,3,0) so it wins regardless of order. */\n.cn-context-menu-root > .cn-context-menu {\n\t/* Hide the NcActions trigger button — menu opens only via right-click.\n\t Off-screen rather than display:none / visibility:hidden so NcPopover's\n\t a11y check (`tabbable(triggerContainer)[0]`) still finds the NcButton\n\t and the \"custom button as a <NcPopover> trigger\" warning stays quiet.\n\t Avoid `clip` / `clip-path` here — modern browsers' `Element.checkVisibility()`,\n\t which tabbable calls into, treats those as invisible. Pure offscreen\n\t positioning leaves the element fully \"visible\" to layout while being\n\t off the user's screen. */\n\tposition: absolute;\n\ttop: -9999px;\n\tleft: -9999px;\n\topacity: 0;\n\tpointer-events: none;\n}\n\n.cn-context-menu__backdrop {\n\tposition: fixed;\n\tinset: 0;\n\tz-index: 9998;\n}\n\n.cn-context-menu__panel {\n\tposition: fixed;\n\ttop: var(--cn-ctx-menu-y, 0);\n\tleft: var(--cn-ctx-menu-x, 0);\n\tz-index: 9999;\n\tmin-width: 220px;\n\tpadding: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 12px);\n\tbox-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);\n\toutline: none;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createBlock","_Fragment","_renderList","_normalizeClass","_toDisplayString","_resolveDynamicComponent","_renderSlot","_createElementVNode"],"mappings":"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"}
|