Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(NcActions): hotfix for custom children #5178

Merged
merged 2 commits into from
Jan 30, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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