Refactor #1451 - For MultiSelect
parent
0823bee377
commit
c8421dcb0f
|
@ -1,4 +1,5 @@
|
||||||
import { VNode } from 'vue';
|
import { VNode } from 'vue';
|
||||||
|
import { VirtualScrollerProps } from '../virtualscroller';
|
||||||
|
|
||||||
interface MultiSelectProps {
|
interface MultiSelectProps {
|
||||||
modelValue?: any;
|
modelValue?: any;
|
||||||
|
@ -25,10 +26,14 @@ interface MultiSelectProps {
|
||||||
emptyMessage?: string;
|
emptyMessage?: string;
|
||||||
display?: string;
|
display?: string;
|
||||||
panelClass?: string;
|
panelClass?: string;
|
||||||
|
selectedItemsLabel?: string;
|
||||||
|
maxSelectedLabels?: number;
|
||||||
selectionLimit?: number;
|
selectionLimit?: number;
|
||||||
showToggleAll?: boolean;
|
showToggleAll?: boolean;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
loadingIcon?: string;
|
loadingIcon?: string;
|
||||||
|
virtualScrollerOptions?: VirtualScrollerProps;
|
||||||
|
selectAll?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare class MultiSelect {
|
declare class MultiSelect {
|
||||||
|
@ -40,6 +45,7 @@ declare class MultiSelect {
|
||||||
$emit(eventName: 'show'): this;
|
$emit(eventName: 'show'): this;
|
||||||
$emit(eventName: 'hide'): this;
|
$emit(eventName: 'hide'): this;
|
||||||
$emit(eventName: 'filter', e: { originalEvent: Event, value: string }): this;
|
$emit(eventName: 'filter', e: { originalEvent: Event, value: string }): this;
|
||||||
|
$emit(eventName: 'selectall-change', e: { originalEvent: Event, checked: boolean }): this;
|
||||||
$slots: {
|
$slots: {
|
||||||
value: VNode[];
|
value: VNode[];
|
||||||
header: VNode[];
|
header: VNode[];
|
||||||
|
|
|
@ -46,46 +46,53 @@
|
||||||
<span class="p-multiselect-close-icon pi pi-times" />
|
<span class="p-multiselect-close-icon pi pi-times" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-multiselect-items-wrapper" :style="{'max-height': scrollHeight}">
|
<div class="p-multiselect-items-wrapper" :style="{'max-height': virtualScrollerDisabled ? scrollHeight : ''}">
|
||||||
<ul class="p-multiselect-items p-component" role="listbox" aria-multiselectable="true">
|
<VirtualScroller :ref="virtualScrollerRef" v-bind="virtualScrollerOptions" :items="visibleOptions" :style="{'height': scrollHeight}" :disabled="virtualScrollerDisabled">
|
||||||
<template v-if="!optionGroupLabel">
|
<template v-slot:content="{ styleClass, contentRef, items, getItemOptions }">
|
||||||
<li v-for="(option, i) of visibleOptions" :class="['p-multiselect-item', {'p-highlight': isSelected(option), 'p-disabled': isOptionDisabled(option)}]" role="option" :aria-selected="isSelected(option)"
|
<ul :ref="contentRef" :class="['p-multiselect-items p-component', styleClass]" role="listbox" aria-multiselectable="true">
|
||||||
:key="getOptionRenderKey(option)" @click="onOptionSelect($event, option)" @keydown="onOptionKeyDown($event, option)" :tabindex="tabindex||'0'" :aria-label="getOptionLabel(option)" v-ripple>
|
<template v-if="!optionGroupLabel">
|
||||||
<div class="p-checkbox p-component">
|
<li v-for="(option, i) of items" :class="['p-multiselect-item', {'p-highlight': isSelected(option), 'p-disabled': isOptionDisabled(option)}]" role="option" :aria-selected="isSelected(option)"
|
||||||
<div :class="['p-checkbox-box', {'p-highlight': isSelected(option)}]">
|
:key="getOptionRenderKey(option)" @click="onOptionSelect($event, option)" @keydown="onOptionKeyDown($event, option)" :tabindex="tabindex||'0'" :aria-label="getOptionLabel(option)" v-ripple>
|
||||||
<span :class="['p-checkbox-icon', {'pi pi-check': isSelected(option)}]"></span>
|
<div class="p-checkbox p-component">
|
||||||
</div>
|
<div :class="['p-checkbox-box', {'p-highlight': isSelected(option)}]">
|
||||||
</div>
|
<span :class="['p-checkbox-icon', {'pi pi-check': isSelected(option)}]"></span>
|
||||||
<slot name="option" :option="option" :index="i">
|
</div>
|
||||||
<span>{{getOptionLabel(option)}}</span>
|
|
||||||
</slot>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<template v-for="(optionGroup, i) of visibleOptions" :key="getOptionGroupRenderKey(optionGroup)">
|
|
||||||
<li class="p-multiselect-item-group">
|
|
||||||
<slot name="optiongroup" :option="optionGroup" :index="i">{{getOptionGroupLabel(optionGroup)}}</slot>
|
|
||||||
</li>
|
|
||||||
<li v-for="(option, i) of getOptionGroupChildren(optionGroup)" :class="['p-multiselect-item', {'p-highlight': isSelected(option), 'p-disabled': isOptionDisabled(option)}]" role="option" :aria-selected="isSelected(option)"
|
|
||||||
:key="getOptionRenderKey(option)" @click="onOptionSelect($event, option)" @keydown="onOptionKeyDown($event, option)" :tabindex="tabindex||'0'" :aria-label="getOptionLabel(option)" v-ripple>
|
|
||||||
<div class="p-checkbox p-component">
|
|
||||||
<div :class="['p-checkbox-box', {'p-highlight': isSelected(option)}]">
|
|
||||||
<span :class="['p-checkbox-icon', {'pi pi-check': isSelected(option)}]"></span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<slot name="option" :option="option" :index="getOptionIndex(i, getItemOptions)">
|
||||||
<slot name="option" :option="option" :index="i">
|
<span>{{getOptionLabel(option)}}</span>
|
||||||
<span>{{getOptionLabel(option)}}</span>
|
</slot>
|
||||||
</slot>
|
</li>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<template v-for="(optionGroup, i) of items" :key="getOptionGroupRenderKey(optionGroup)">
|
||||||
|
<li class="p-multiselect-item-group">
|
||||||
|
<slot name="optiongroup" :option="optionGroup" :index="getOptionIndex(i, getItemOptions)">{{getOptionGroupLabel(optionGroup)}}</slot>
|
||||||
|
</li>
|
||||||
|
<li v-for="(option, i) of getOptionGroupChildren(optionGroup)" :class="['p-multiselect-item', {'p-highlight': isSelected(option), 'p-disabled': isOptionDisabled(option)}]" role="option" :aria-selected="isSelected(option)"
|
||||||
|
:key="getOptionRenderKey(option)" @click="onOptionSelect($event, option)" @keydown="onOptionKeyDown($event, option)" :tabindex="tabindex||'0'" :aria-label="getOptionLabel(option)" v-ripple>
|
||||||
|
<div class="p-checkbox p-component">
|
||||||
|
<div :class="['p-checkbox-box', {'p-highlight': isSelected(option)}]">
|
||||||
|
<span :class="['p-checkbox-icon', {'pi pi-check': isSelected(option)}]"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<slot name="option" :option="option" :index="getOptionIndex(i, getItemOptions)">
|
||||||
|
<span>{{getOptionLabel(option)}}</span>
|
||||||
|
</slot>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<li v-if="filterValue && (!items || (items && items.length === 0))" class="p-multiselect-empty-message">
|
||||||
|
<slot name="emptyfilter">{{emptyFilterMessageText}}</slot>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
<li v-else-if="(!options || (options && options.length === 0))" class="p-multiselect-empty-message">
|
||||||
|
<slot name="empty">{{emptyMessageText}}</slot>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
<li v-if="filterValue && (!visibleOptions || (visibleOptions && visibleOptions.length === 0))" class="p-multiselect-empty-message">
|
<template v-slot:loader="{ options }" v-if="$slots.loader">
|
||||||
<slot name="emptyfilter">{{emptyFilterMessageText}}</slot>
|
<slot name="loader" :options="options"></slot>
|
||||||
</li>
|
</template>
|
||||||
<li v-else-if="(!options || (options && options.length === 0))" class="p-multiselect-empty-message">
|
</VirtualScroller>
|
||||||
<slot name="empty">{{emptyMessageText}}</slot>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<slot name="footer" :value="modelValue" :options="visibleOptions"></slot>
|
<slot name="footer" :value="modelValue" :options="visibleOptions"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -99,10 +106,11 @@ import {ConnectedOverlayScrollHandler,ObjectUtils,DomHandler,ZIndexUtils} from '
|
||||||
import OverlayEventBus from 'primevue/overlayeventbus';
|
import OverlayEventBus from 'primevue/overlayeventbus';
|
||||||
import {FilterService} from 'primevue/api';
|
import {FilterService} from 'primevue/api';
|
||||||
import Ripple from 'primevue/ripple';
|
import Ripple from 'primevue/ripple';
|
||||||
|
import VirtualScroller from 'primevue/virtualscroller';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MultiSelect',
|
name: 'MultiSelect',
|
||||||
emits: ['update:modelValue', 'before-show', 'before-hide', 'change', 'show', 'hide', 'filter'],
|
emits: ['update:modelValue', 'before-show', 'before-hide', 'change', 'show', 'hide', 'filter', 'selectall-change'],
|
||||||
props: {
|
props: {
|
||||||
modelValue: null,
|
modelValue: null,
|
||||||
options: Array,
|
options: Array,
|
||||||
|
@ -149,6 +157,14 @@ export default {
|
||||||
default: 'comma'
|
default: 'comma'
|
||||||
},
|
},
|
||||||
panelClass: null,
|
panelClass: null,
|
||||||
|
selectedItemsLabel: {
|
||||||
|
type: String,
|
||||||
|
default: '{0} items selected'
|
||||||
|
},
|
||||||
|
maxSelectedLabels: {
|
||||||
|
type: Number,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
selectionLimit: {
|
selectionLimit: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: null
|
default: null
|
||||||
|
@ -164,6 +180,14 @@ export default {
|
||||||
loadingIcon: {
|
loadingIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'pi pi-spinner pi-spin'
|
default: 'pi pi-spinner pi-spin'
|
||||||
|
},
|
||||||
|
virtualScrollerOptions: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
selectAll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -178,6 +202,7 @@ export default {
|
||||||
resizeListener: null,
|
resizeListener: null,
|
||||||
scrollHandler: null,
|
scrollHandler: null,
|
||||||
overlay: null,
|
overlay: null,
|
||||||
|
virtualScroller: null,
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.unbindOutsideClickListener();
|
this.unbindOutsideClickListener();
|
||||||
this.unbindResizeListener();
|
this.unbindResizeListener();
|
||||||
|
@ -193,6 +218,9 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getOptionIndex(index, fn) {
|
||||||
|
return this.virtualScrollerDisabled ? index : (fn && fn(index)['index']);
|
||||||
|
},
|
||||||
getOptionLabel(option) {
|
getOptionLabel(option) {
|
||||||
return this.optionLabel ? ObjectUtils.resolveFieldData(option, this.optionLabel) : option;
|
return this.optionLabel ? ObjectUtils.resolveFieldData(option, this.optionLabel) : option;
|
||||||
},
|
},
|
||||||
|
@ -218,20 +246,35 @@ export default {
|
||||||
|
|
||||||
return this.optionDisabled ? ObjectUtils.resolveFieldData(option, this.optionDisabled) : false;
|
return this.optionDisabled ? ObjectUtils.resolveFieldData(option, this.optionDisabled) : false;
|
||||||
},
|
},
|
||||||
isSelected(option) {
|
getSelectedOptionIndex() {
|
||||||
let selected = false;
|
if (this.modelValue != null && this.options) {
|
||||||
let optionValue = this.getOptionValue(option);
|
if (this.optionGroupLabel) {
|
||||||
|
for (let i = 0; i < this.options.length; i++) {
|
||||||
if (this.modelValue) {
|
let selectedOptionIndex = this.findOptionIndexInList(this.modelValue, this.getOptionGroupChildren(this.options[i]));
|
||||||
for (let val of this.modelValue) {
|
if (selectedOptionIndex !== -1) {
|
||||||
if (ObjectUtils.equals(val, optionValue, this.equalityKey)) {
|
return {group: i, option: selectedOptionIndex};
|
||||||
selected = true;
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
return this.findOptionIndexInList(this.modelValue, this.options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return selected;
|
return -1;
|
||||||
|
},
|
||||||
|
findOptionIndexInList(value, list) {
|
||||||
|
return value ? list.findIndex(item => value.some(val => ObjectUtils.equals(val, this.getOptionValue(item), this.equalityKey))) : -1;
|
||||||
|
},
|
||||||
|
isSelected(option) {
|
||||||
|
if (this.modelValue) {
|
||||||
|
let optionValue = this.getOptionValue(option);
|
||||||
|
let key = this.equalityKey;
|
||||||
|
|
||||||
|
return this.modelValue.some(val => ObjectUtils.equals(val, optionValue, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
show() {
|
show() {
|
||||||
this.$emit('before-show');
|
this.$emit('before-show');
|
||||||
|
@ -317,7 +360,7 @@ export default {
|
||||||
if (selected)
|
if (selected)
|
||||||
value = this.modelValue.filter(val => !ObjectUtils.equals(val, this.getOptionValue(option), this.equalityKey));
|
value = this.modelValue.filter(val => !ObjectUtils.equals(val, this.getOptionValue(option), this.equalityKey));
|
||||||
else
|
else
|
||||||
value = [...this.modelValue || [], this.getOptionValue(option)];
|
value = [...(this.modelValue || []), this.getOptionValue(option)];
|
||||||
|
|
||||||
this.$emit('update:modelValue', value);
|
this.$emit('update:modelValue', value);
|
||||||
this.$emit('change', {originalEvent: event, value: value});
|
this.$emit('change', {originalEvent: event, value: value});
|
||||||
|
@ -383,6 +426,13 @@ export default {
|
||||||
this.$refs.filterInput.focus();
|
this.$refs.filterInput.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.virtualScrollerDisabled) {
|
||||||
|
const selectedIndex = this.getSelectedOptionIndex();
|
||||||
|
if (selectedIndex !== -1) {
|
||||||
|
this.virtualScroller.scrollToIndex(selectedIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit('show');
|
this.$emit('show');
|
||||||
},
|
},
|
||||||
onOverlayLeave() {
|
onOverlayLeave() {
|
||||||
|
@ -484,24 +534,37 @@ export default {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
getSelectedItemsLabel() {
|
||||||
|
let pattern = /{(.*?)}/;
|
||||||
|
if (pattern.test(this.selectedItemsLabel)) {
|
||||||
|
return this.selectedItemsLabel.replace(this.selectedItemsLabel.match(pattern)[0], this.modelValue.length + '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.selectedItemsLabel;
|
||||||
|
},
|
||||||
onToggleAll(event) {
|
onToggleAll(event) {
|
||||||
let value = null;
|
if (this.selectAll !== null) {
|
||||||
|
this.$emit('selectall-change', {originalEvent: event, checked: !this.allSelected});
|
||||||
if (this.allSelected) {
|
|
||||||
value = [];
|
|
||||||
}
|
}
|
||||||
else if (this.visibleOptions) {
|
else {
|
||||||
if (this.optionGroupLabel) {
|
let value = null;
|
||||||
|
|
||||||
|
if (this.allSelected) {
|
||||||
value = [];
|
value = [];
|
||||||
this.visibleOptions.forEach(optionGroup => value = [...value, ...this.getOptionGroupChildren(optionGroup)]);
|
|
||||||
}
|
}
|
||||||
else {
|
else if (this.visibleOptions) {
|
||||||
value = this.visibleOptions.map(option => this.getOptionValue(option));
|
if (this.optionGroupLabel) {
|
||||||
|
value = [];
|
||||||
|
this.visibleOptions.forEach(optionGroup => value = [...value, ...this.getOptionGroupChildren(optionGroup)]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
value = this.visibleOptions.map(option => this.getOptionValue(option));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.$emit('update:modelValue', value);
|
this.$emit('update:modelValue', value);
|
||||||
this.$emit('change', {originalEvent: event, value: value});
|
this.$emit('change', {originalEvent: event, value: value});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onFilterChange(event) {
|
onFilterChange(event) {
|
||||||
this.$emit('filter', {originalEvent: event, value: event.target.value});
|
this.$emit('filter', {originalEvent: event, value: event.target.value});
|
||||||
|
@ -512,6 +575,9 @@ export default {
|
||||||
overlayRef(el) {
|
overlayRef(el) {
|
||||||
this.overlay = el;
|
this.overlay = el;
|
||||||
},
|
},
|
||||||
|
virtualScrollerRef(el) {
|
||||||
|
this.virtualScroller = el;
|
||||||
|
},
|
||||||
removeChip(item) {
|
removeChip(item) {
|
||||||
let value = this.modelValue.filter(val => !ObjectUtils.equals(val, item, this.equalityKey));
|
let value = this.modelValue.filter(val => !ObjectUtils.equals(val, item, this.equalityKey));
|
||||||
|
|
||||||
|
@ -571,13 +637,18 @@ export default {
|
||||||
let label;
|
let label;
|
||||||
|
|
||||||
if (this.modelValue && this.modelValue.length) {
|
if (this.modelValue && this.modelValue.length) {
|
||||||
label = '';
|
if (!this.maxSelectedLabels || this.modelValue.length <= this.maxSelectedLabels) {
|
||||||
for(let i = 0; i < this.modelValue.length; i++) {
|
label = '';
|
||||||
if(i !== 0) {
|
for(let i = 0; i < this.modelValue.length; i++) {
|
||||||
label += ', ';
|
if(i !== 0) {
|
||||||
}
|
label += ', ';
|
||||||
|
}
|
||||||
|
|
||||||
label += this.getLabelByValue(this.modelValue[i]);
|
label += this.getLabelByValue(this.modelValue[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return this.getSelectedItemsLabel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -587,42 +658,47 @@ export default {
|
||||||
return label;
|
return label;
|
||||||
},
|
},
|
||||||
allSelected() {
|
allSelected() {
|
||||||
if (this.filterValue && this.filterValue.trim().length > 0) {
|
if (this.selectAll !== null) {
|
||||||
if (this.visibleOptions.length === 0) {
|
return this.selectAll;
|
||||||
return false;
|
}
|
||||||
}
|
else {
|
||||||
|
if (this.filterValue && this.filterValue.trim().length > 0) {
|
||||||
|
if (this.visibleOptions.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.optionGroupLabel) {
|
if (this.optionGroupLabel) {
|
||||||
for (let optionGroup of this.visibleOptions) {
|
for (let optionGroup of this.visibleOptions) {
|
||||||
for (let option of this.getOptionGroupChildren(optionGroup)) {
|
for (let option of this.getOptionGroupChildren(optionGroup)) {
|
||||||
|
if (!this.isSelected(option)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (let option of this.visibleOptions) {
|
||||||
if (!this.isSelected(option)) {
|
if (!this.isSelected(option)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (let option of this.visibleOptions) {
|
if (this.modelValue && this.options) {
|
||||||
if (!this.isSelected(option)) {
|
let optionCount = 0;
|
||||||
return false;
|
if (this.optionGroupLabel)
|
||||||
}
|
this.options.forEach(optionGroup => optionCount += this.getOptionGroupChildren(optionGroup).length);
|
||||||
|
else
|
||||||
|
optionCount = this.options.length;
|
||||||
|
|
||||||
|
return optionCount > 0 && optionCount === this.modelValue.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (this.modelValue && this.options) {
|
|
||||||
let optionCount = 0;
|
|
||||||
if (this.optionGroupLabel)
|
|
||||||
this.options.forEach(optionGroup => optionCount += this.getOptionGroupChildren(optionGroup).length);
|
|
||||||
else
|
|
||||||
optionCount = this.options.length;
|
|
||||||
|
|
||||||
return optionCount > 0 && optionCount === this.modelValue.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
equalityKey() {
|
equalityKey() {
|
||||||
|
@ -643,6 +719,9 @@ export default {
|
||||||
appendTarget() {
|
appendTarget() {
|
||||||
return this.appendDisabled ? null : this.appendTo;
|
return this.appendDisabled ? null : this.appendTo;
|
||||||
},
|
},
|
||||||
|
virtualScrollerDisabled() {
|
||||||
|
return !this.virtualScrollerOptions;
|
||||||
|
},
|
||||||
maxSelectionLimitReached() {
|
maxSelectionLimitReached() {
|
||||||
return this.selectionLimit && (this.modelValue && this.modelValue.length === this.selectionLimit);
|
return this.selectionLimit && (this.modelValue && this.modelValue.length === this.selectionLimit);
|
||||||
},
|
},
|
||||||
|
@ -652,6 +731,9 @@ export default {
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
'ripple': Ripple
|
'ripple': Ripple
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
'VirtualScroller': VirtualScroller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue