Skip to content

Commit

Permalink
chore(lib): update Menu
Browse files Browse the repository at this point in the history
- `Menu` migrates to Vue 3. Changes sufficient to render the
  documentation page of `Menu` are made.
- A new concept "menu item container" that abstracts a container of
  `MenuItem`s is introduced, and it is provided as `BMenuItemContainer`
  defined in a new file `src/components/menu/MenuItemContainerMixin.js`.
  `Menu` and `MenuItem` are a `BMenuItemContainer`. `MenuItem` used to
  depend on `$children` of its parent to list its siblings, but Vue 3
  no longer provides `$children`. Thus, the concept "menu item
  container" is introduced. A `MenuItem` actively registers itself to
  the immediate parent "menu item container" when it is mounted. A
  `MenuItem` reads parent's "menu items" to access its siblings.
- In `src/components/menu/Menu.vue`,
    - `MenuItemContainerMixin` (see above) is mixed in.
- In `src/components/menu/MenuItem.vue`,
    - `MenuItemContainerMixin` (see above) is mixed in.
    - `MenuItem` `inject`s `BMenuItemContainer` as `parent`.
    - `MenuItem` reads `parent`'s `menuItems` instead of `$children`
      to access its siblings. Note that `parent` is not equivalent to
      `$parent`. For instance, when a `MenuItem` is wrapped inside a
      `transition` its `$parent` is neither a `Menu` nor `MenuItem`
      but a `Transition`.
    - `MenuItem` appends itself to its parent when it is mounted. And
      it removes itself from its parent when it is about to be
      umounted.
    - Events to be emitted are listed in `emits`.
    - Binding of `$listeners` is removed because Vue 3 no longer
      provides `$listeners`. Listeners are included in `$attrs`.
- In `src/components/menu/MenuList.vue`,
    - `MenuList` becomes a new functional component conforms to Vue 3.
    - Resolves the `b-icon` component with `resolveComponent`.
- Common,
    - Automatic ESLinst fix is applied.
  • Loading branch information
kikuomax committed Jul 7, 2023
1 parent bf6e9cd commit f85445f
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 39 deletions.
3 changes: 3 additions & 0 deletions src/components/menu/Menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
</template>

<script>
import MenuItemContainerMixin from './MenuItemContainerMixin'
export default {
name: 'BMenu',
mixins: [MenuItemContainerMixin],
props: {
accordion: {
type: Boolean,
Expand Down
25 changes: 21 additions & 4 deletions src/components/menu/MenuItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
'icon-text': icon,
}"
@click="onClick($event)"
v-on="$listeners">
>
<b-icon
v-if="icon"
:icon="icon"
Expand Down Expand Up @@ -39,12 +39,20 @@
<script>
import Icon from '../icon/Icon.vue'
import config from '../../utils/config'
import MenuItemContainerMixin from './MenuItemContainerMixin'
export default {
name: 'BMenuItem',
components: {
[Icon.name]: Icon
},
mixins: [MenuItemContainerMixin],
inject: {
parent: {
from: 'BMenuItemContainer',
default: null
}
},
inheritAttrs: false,
// deprecated, to replace with default 'value' in the next breaking change
model: {
Expand Down Expand Up @@ -78,6 +86,7 @@ export default {
default: 'is-small'
}
},
emits: ['update:active', 'update:expanded'],
data() {
return {
newActive: this.active,
Expand All @@ -101,7 +110,7 @@ export default {
onClick(event) {
if (this.disabled) return
const menu = this.getMenu()
this.reset(this.$parent, menu)
this.reset(this.parent, menu)
this.newExpanded = this.$props.expanded || !this.newExpanded
this.$emit('update:expanded', this.newExpanded)
if (menu && menu.activable) {
Expand All @@ -110,8 +119,10 @@ export default {
}
},
reset(parent, menu) {
const items = parent.$children.filter((c) => c.name === this.name)
items.forEach((item) => {
if (parent == null) {
return
}
parent.menuItems.forEach((item) => {
if (item !== this) {
this.reset(item, menu)
if (!parent.$data._isMenu || (parent.$data._isMenu && parent.accordion)) {
Expand All @@ -132,6 +143,12 @@ export default {
}
return parent
}
},
mounted() {
this.parent?.appendMenuItem(this)
},
beforeUnmount() {
this.parent?.removeMenuItem(this)
}
}
</script>
23 changes: 23 additions & 0 deletions src/components/menu/MenuItemContainerMixin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export default {
provide() {
return {
BMenuItemContainer: this
}
},
data() {
return {
menuItems: []
}
},
methods: {
appendMenuItem(item) {
this.menuItems.push(item)
},
removeMenuItem(item) {
const index = this.menuItems.indexOf(item)
if (index !== -1) {
this.menuItems.splice(index, 1)
}
}
}
}
80 changes: 45 additions & 35 deletions src/components/menu/MenuList.vue
Original file line number Diff line number Diff line change
@@ -1,41 +1,51 @@
<script>
export default {
name: 'BMenuList',
functional: true,
props: {
label: String,
icon: String,
iconPack: String,
ariaRole: {
type: String,
default: ''
import { h as createElement, resolveComponent } from 'vue'
const BMenuList = (props, context) => {
let vlabel = null
const slots = context.slots
if (props.label || slots.label) {
vlabel = createElement(
'p',
{ class: 'menu-label' },
props.label
? props.icon
? [
createElement(resolveComponent('b-icon'), {
icon: props.icon,
pack: props.iconPack,
size: props.size
}),
createElement('span', {}, props.label)
]
: props.label
: slots.label()
)
}
const vnode = createElement(
'ul',
{
class: 'menu-list',
role: props.ariaRole === 'menu' ? props.ariaRole : null
},
size: {
type: String,
default: 'is-small'
}
slots.default()
)
return vlabel ? [vlabel, vnode] : vnode
}
BMenuList.props = {
label: String,
icon: String,
iconPack: String,
ariaRole: {
type: String,
default: ''
},
render(createElement, context) {
let vlabel = null
const slots = context.slots()
if (context.props.label || slots.label) {
vlabel = createElement('p', { attrs: { 'class': 'menu-label' } },
context.props.label
? context.props.icon
? [
createElement('b-icon', {
props: {
'icon': context.props.icon,
'pack': context.props.iconPack,
'size': context.props.size
}
}),
createElement('span', {}, context.props.label)
] : context.props.label
: slots.label)
}
const vnode = createElement('ul', { attrs: { 'class': 'menu-list', 'role': context.props.ariaRole === 'menu' ? context.props.ariaRole : null } }, slots.default)
return vlabel ? [ vlabel, vnode ] : vnode
size: {
type: String,
default: 'is-small'
}
}
export default BMenuList
</script>

0 comments on commit f85445f

Please sign in to comment.