Skip to content

Commit

Permalink
Merge pull request #5178 from nextcloud-libraries/fix/nc-actions--cus…
Browse files Browse the repository at this point in the history
…tom-children

fix(NcActions): hotfix for custom children
  • Loading branch information
ShGKme authored Jan 30, 2024
2 parents 102db32 + 5b951e5 commit 4a2c155
Showing 1 changed file with 147 additions and 3 deletions.
150 changes: 147 additions & 3 deletions src/components/NcActions/NcActions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,131 @@ p {
}
</style>
```

## NcActions children limitations

`<NcActions>` is supposed to be used with direct `<NcAction*>` children.
Although it works when actions are not direct children but wrapped in custom components, it has limitations:
- No `inline` prop property, including a single action display;
- Accessibility issues, including changed keyboard behavior;
- Invalid HTML.

```
<template>
<table class="actions-limitations-table">
<tr>
<th style="width: 50%">Non-direct children</th>
<th style="width: 50%">Direct NcAction* children</th>
</tr>

<tr>
<th colspan="2">This single button is supposed to be rendered as inline action but it is rendered as a menu:</th>
</tr>
<tr>
<td>
<NcActions>
<MyUserActionButton />
</NcActions>
</td>
<td>
<NcActions>
<NcActionButton>
<template #icon>
<Account :size="20" />
</template>
Button
</NcActionButton>
</NcActions>
</td>
</tr>
<tr>
<th colspan="2">This NcActions is supposed to have 2 inline buttons but it has none:</th>
</tr>
<tr>
<td>
<NcActions :inline="2">
<MyUserActionButton v-for="i in 4" />
</NcActions>
</td>
<td>
<NcActions :inline="2">
<NcActionButton v-for="i in 4">
<template #icon>
<Account :size="20" />
</template>
Button
</NcActionButton>
</NcActions>
</td>
</tr>
<tr>
<th colspan="2">This NcActions is supposed to have a11y role menu and keyboard navigation but it acts like a dialog:</th>
</tr>
<tr>
<td>
<NcActions>
<MyActionsList />
</NcActions>
</td>
<td>
<NcActions>
<NcActionButton v-for="i in 4">
<template #icon>
<Account :size="20" />
</template>
Button
</NcActionButton>
</NcActions>
</td>
</tr>
</table>
</template>

<script>
import Account from 'vue-material-design-icons/Account.vue'
export default {
components: {
Account,
MyUserActionButton: {
name: 'MyUserActionButton',
components: { Account },
render: (h) => h('NcActionButton', ['Button', h('Account', { props: { size: 20 }, slot: 'icon' })]),
},
MyActionsList: {
name: 'MyActionsList',
components: { Account },
render: (h) => h('div', [
h('NcActionButton', ['Button', h('Account', { props: { size: 20 }, slot: 'icon' })]),
h('NcActionButton', ['Button', h('Account', { props: { size: 20 }, slot: 'icon' })]),
h('NcActionButton', ['Button', h('Account', { props: { size: 20 }, slot: 'icon' })]),
h('NcActionButton', ['Button', h('Account', { props: { size: 20 }, slot: 'icon' })]),
]),
}
}
}
</script>

<style>
.actions-limitations-table {
border-collapse: collapse;
width: 100%;
th,
td {
border: 1px solid var(--color-border);
padding: var(--default-grid-baseline);
max-width: 50%;
}
th {
text-align: center;
text-wrap: wrap;
}
}
</style>
```
</docs>

<script>
Expand Down Expand Up @@ -1063,7 +1188,7 @@ export default {
*
* We need to pause all the focus traps for opening popover and then unpause them back after closing.
*/
intersectIntoCurrentFocusTrapStack() {
intersectIntoCurrentFocusTrapStack() {
if (this.withFocusTrap) {
return
}
Expand Down Expand Up @@ -1290,7 +1415,11 @@ export default {
// When there is no focusable elements to handle Tab press from actions menu
// It requries manual closing
if (this.actionsMenuSemanticType === 'tooltip') {
this.closeMenu(false)
// Tooltip is supposed to have no focusable element.
// However, if there is a custom focusable element, it will be auto-focused and cause the menu to be closed on open.
if (this.$refs.menu && this.$refs.menu.querySelectorAll(focusableSelector).length === 0) {
this.closeMenu(false)
}
}
},
onClick(event) {
Expand Down Expand Up @@ -1366,7 +1495,22 @@ export default {
} else if (hasLinkAction) {
this.actionsMenuSemanticType = 'navigation'
} else {
this.actionsMenuSemanticType = 'tooltip'
// (!) Hotfix (!)
// In Vue 2 it is not easy to search for NcAction* in sub-component of a slot.
// When a menu is rendered, children are not mounted yet.
// If we have NcActions > MyActionsList > NcActionButton, only MyActionsList's vnode is available.
// So when NcActions has actions as non-direct children, here then we don't know about them.
// Like this menu has no buttons/links/inputs.
// It makes the menu incorrectly considered a tooltip.
const ncActions = actions.filter((action) => this.getActionName(action).startsWith('NcAction'))
if (ncActions.length === actions.length) {
// True tooltip
this.actionsMenuSemanticType = 'tooltip'
} else {
// Custom components are passed to the NcActions
// dialog is a universal fallback option
this.actionsMenuSemanticType = 'dialog'
}
}
const actionsRoleToHtmlPopupRole = {
Expand Down

0 comments on commit 4a2c155

Please sign in to comment.