Skip to content

Commit

Permalink
chore(lib): update Steps
Browse files Browse the repository at this point in the history
- `Steps` migrates to Vue 3. Utilities `InjectedChildMixin`,
  `ProviderParentMixin`, `TabbedChildMixin`, and `TabbedMixin` are
  also changed. Changes sufficient to render the documentation page
  of `Steps` are made. A commint of updates of the documentation page
  will follow this commit.
- In `src/components/steps/Steps.vue`,
    - The `disabled` bindings of navigation buttons are fixed. Because
      setting a boolean attribute to `false` does not remove it,
      `null` or `undefined` has to be given to remove it.
- In `src/utils/InjectedChildMixin.js`,
    - A new prop `order` is introduced. This controls the ordering of
      `StepItem`s in `Steps`. It used to be controlled by the index
      in `$scopedSlots.default`, but I give it up because it seems no
      longer possible to enumerate component instances in a slot.
    - A new field `dynamicIndex` is added to `data`. Despite I give
      enumerating `StepItem`s up, automatic indexing still works in a
      limited situation where no child is unmounted after it is
      mounted. `dynamicIndex` of an item is assigned when the item is
      registered to `ProviderParentMixin` (see below).
    - `index` becomes a computed property, and it becomes `order` if
      `order` is defined, but `dynamicIndex` otherwise.
    - Note that `InjectedChildMixin` is mixed in to `StepItem` via
      `TabbedChildMixin`.
- In `src/utils/ProviderParentMixin.js`,
    - A new field `nextIndex` is introduced to `data`. This is used to
      determine an index of a child item to be registered.
    - The watch trigger of `childItems` is no longer used to update
      indices of children. There are two reasons why I give it up,
        1. It seems that there is no way to enumerate child components
           in a slot, as I mentioned above.
        2. The watch trigger of `childItems` is fired only once when
           it is initialized; i.e., fired with an empty array. A
           workaround may be using a deep watch trigger, though it will
           be pricy.
    - Note that `ProviderParentMixin` is mixed in to `StepItem` via
      `TabbedMixin`.
- In `src/utils/TabbedChildMixin.js`,
    - A `v-show` directive used to be simulated by styling `display`.
      It prevented `transition` from working. I found that
      `withDirectives` is a proper way to add a directive, and the
      simulation is replaced with `withDirectives`.
    - A built-in `Transition` component is directly specified instead
      of giving a name "transition", because the name "transition" is
      no longer resolved on Vue 3.
    - Note that `TabbedChildMixin` is mixed in to `StepItem`.
- In `src/utils/TabbedMixin.js`,
    - The default `v-model` binding conforms to Vue 3.
        - `value` prop --> `modelValue`
        - `input` event --> `update:modelValue`
    - Note that `TabbedMixin` is mixed in to `Steps`.
- Common,
    - ESLint fix is applied.
  • Loading branch information
kikuomax committed Jul 7, 2023
1 parent 10a8999 commit defe379
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 81 deletions.
34 changes: 21 additions & 13 deletions src/components/steps/Steps.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@
:class="[childItem.type || type, childItem.headerClass, {
'is-active': childItem.isActive,
'is-previous': activeItem.index > childItem.index
}]">
}]"
>
<a
class="step-link"
:class="{'is-clickable': isItemClickable(childItem)}"
@click="isItemClickable(childItem) && childClick(childItem)">
@click="isItemClickable(childItem) && childClick(childItem)"
>
<div class="step-marker">
<b-icon
v-if="childItem.icon"
:icon="childItem.icon"
:pack="childItem.iconPack"
:size="size"/>
:size="size"
/>
<span v-else-if="childItem.step">{{ childItem.step }}</span>
</div>
<div class="step-details">
Expand All @@ -31,36 +34,41 @@
</ul>
</nav>
<section class="step-content" :class="{'is-transitioning': isTransitioning}">
<slot/>
<slot />
</section>
<slot
name="navigation"
:previous="navigationProps.previous"
:next="navigationProps.next">
:next="navigationProps.next"
>
<nav v-if="hasNavigation" class="step-navigation">
<a
role="button"
class="pagination-previous"
:disabled="navigationProps.previous.disabled"
:disabled="navigationProps.previous.disabled || undefined"
@click.prevent="navigationProps.previous.action"
:aria-label="ariaPreviousLabel">
:aria-label="ariaPreviousLabel"
>
<b-icon
:icon="iconPrev"
:pack="iconPack"
both
aria-hidden="true"/>
aria-hidden="true"
/>
</a>
<a
role="button"
class="pagination-next"
:disabled="navigationProps.next.disabled"
:disabled="navigationProps.next.disabled || undefined"
@click.prevent="navigationProps.next.action"
:aria-label="ariaNextLabel">
:aria-label="ariaNextLabel"
>
<b-icon
:icon="iconNext"
:pack="iconPack"
both
aria-hidden="true"/>
aria-hidden="true"
/>
</a>
</nav>
</slot>
Expand Down Expand Up @@ -164,7 +172,7 @@ export default {
* Retrieves the next visible item index
*/
nextItemIdx() {
let idx = this.activeItem ? this.items.indexOf(this.activeItem) : 0
const idx = this.activeItem ? this.items.indexOf(this.activeItem) : 0
return this.getNextItemIdx(idx)
},
Expand All @@ -184,7 +192,7 @@ export default {
*/
prevItemIdx() {
if (!this.activeItem) { return null }
let idx = this.items.indexOf(this.activeItem)
const idx = this.items.indexOf(this.activeItem)
return this.getPrevItemIdx(idx)
},
Expand Down
20 changes: 19 additions & 1 deletion src/utils/InjectedChildMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,27 @@ export default (parentItemName, flags = 0) => {
}
}
if (hasFlag(flags, sorted)) {
// a user can explicitly specify the `order` prop to keep the order of
// children.
// I can no longer rely on automatic indexing of children, because I
// could not figure out how to calculate the index of a child in its
// parent on Vue 3.
// incomplete dynamic indexing is still available if any child is never
// unmounted; e.g., not switched with `v-if`
mixin.props = {
order: {
type: Number,
required: false
}
}
mixin.data = () => {
return {
index: null
dynamicIndex: null
}
}
mixin.computed = {
index() {
return this.order != null ? this.order : this.dynamicIndex
}
}
}
Expand Down
49 changes: 11 additions & 38 deletions src/utils/ProviderParentMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,55 +18,28 @@ export default (itemName, flags = 0) => {
if (hasFlag(flags, items)) {
mixin.data = function () {
return {
childItems: []
childItems: [],
...(hasFlag(flags, sorted) ? { nextIndex: 0 } : {})
}
}
mixin.methods = {
_registerItem(item) {
if (hasFlag(flags, sorted)) {
// assigns a dynamic index.
// dynamic indices will be messed up if any child is
// unmounted.
// use the new `order` prop to maintain the ordering.
item.dynamicIndex = this.nextIndex
++this.nextIndex
}
this.childItems.push(item)
},
_unregisterItem(item) {
this.childItems = this.childItems.filter((i) => i !== item)
this.childItems = this.childItems.filter((i) => i.value !== item.value)
}
}

if (hasFlag(flags, sorted)) {
mixin.watch = {
/**
* When items are added/removed deep search in the elements default's slot
* And mark the items with their index
*/
childItems(items) {
if (items.length > 0 && this.$scopedSlots.default) {
const tag = items[0].$vnode.tag
let index = 0

const deepSearch = (children) => {
for (const child of children) {
if (child.tag === tag) {
// An item with the same tag will for sure be found
const it = items.find((i) => i.$vnode === child)
if (it) {
it.index = index++
}
} else if (child.tag) {
const sub = child.componentInstance
? (child.componentInstance.$scopedSlots.default
? child.componentInstance.$scopedSlots.default()
: child.componentInstance.$children)
: child.children
if (Array.isArray(sub) && sub.length > 0) {
deepSearch(sub.map((e) => e.$vnode))
}
}
}
return false
}

deepSearch(this.$scopedSlots.default())
}
}
}
mixin.computed = {
/**
* When items are added/removed sort them according to their position
Expand Down
49 changes: 29 additions & 20 deletions src/utils/TabbedChildMixin.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { h as createElement } from 'vue'
import { h as createElement, Transition, vShow, withDirectives } from 'vue'

import InjectedChildMixin, { Sorted } from './InjectedChildMixin'
import { makeUniqueId } from './make-unique-id'
Expand Down Expand Up @@ -60,27 +60,36 @@ export default (parentCmp) => ({
return
}
}
const vnode = createElement('div', {
// simulates v-show
style: {
display: this.isActive && this.visible ? '' : 'none'
},
// NOTE: possible regression of #3272
// https://github.com/buefy/buefy/issues/3272
class: this.elementClass,
role: this.elementRole,
id: `${this.value}-content`,
'aria-labelledby': this.elementRole ? `${this.value}-label` : null,
tabindex: this.isActive ? 0 : -1
}, this.$slots.default())
const vnode = withDirectives(
createElement(
'div',
{
// NOTE: possible regression of #3272
// https://github.com/buefy/buefy/issues/3272
class: this.elementClass,
role: this.elementRole,
id: `${this.value}-content`,
'aria-labelledby': this.elementRole
? `${this.value}-label`
: null,
tabindex: this.isActive ? 0 : -1
},
this.$slots
),
[[vShow, this.isActive && this.visible]]
)
// check animated prop
if (this.parent.animated) {
return createElement('transition', {
name: this.parent.animation || this.transitionName,
appear: this.parent.animateInitially === true || undefined,
onBeforeEnter: () => { this.parent.isTransitioning = true },
onAfterEnter: () => { this.parent.isTransitioning = false }
}, [vnode])
return createElement(
Transition,
{
name: this.parent.animation || this.transitionName,
appear: this.parent.animateInitially === true || undefined,
onBeforeEnter: () => { this.parent.isTransitioning = true },
onAfterEnter: () => { this.parent.isTransitioning = false }
},
{ default: () => vnode }
)
}
return vnode
}
Expand Down
19 changes: 10 additions & 9 deletions src/utils/TabbedMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default (cmp) => ({
[SlotComponent.name]: SlotComponent
},
props: {
value: {
modelValue: {
type: [String, Number],
default: undefined
},
Expand All @@ -31,21 +31,22 @@ export default (cmp) => ({
default: false
}
},
emits: ['update:modelValue'],
data() {
return {
activeId: this.value, // Internal state
activeId: this.modelValue, // Internal state
defaultSlots: [],
contentHeight: 0,
isTransitioning: false
}
},
mounted() {
if (typeof this.value === 'number') {
if (typeof this.modelValue === 'number') {
// Backward compatibility: converts the index value to an id
const value = bound(this.value, 0, this.items.length - 1)
const value = bound(this.modelValue, 0, this.items.length - 1)
this.activeId = this.items[value].value
} else {
this.activeId = this.value
this.activeId = this.modelValue
}
},
computed: {
Expand All @@ -64,7 +65,7 @@ export default (cmp) => ({
/**
* When v-model is changed set the new active tab.
*/
value(value) {
modelValue(value) {
if (typeof value === 'number') {
// Backward compatibility: converts the index value to an id
value = bound(value, 0, this.items.length - 1)
Expand All @@ -87,11 +88,11 @@ export default (cmp) => ({
}

val = this.activeItem
? (typeof this.value === 'number' ? this.items.indexOf(this.activeItem) : this.activeItem.value)
? (typeof this.modelValue === 'number' ? this.items.indexOf(this.activeItem) : this.activeItem.value)
: undefined

if (val !== this.value) {
this.$emit('input', val)
if (val !== this.modelValue) {
this.$emit('update:modelValue', val)
}
}
},
Expand Down

0 comments on commit defe379

Please sign in to comment.