feat: mentions

pull/2468/head
Amour1688 2020-06-20 10:08:45 +08:00
parent 0910df5a95
commit a53768fa9a
9 changed files with 92 additions and 116 deletions

View File

@ -35,3 +35,7 @@ v-model -> v-model:visible
v-model -> v-model:visible v-model -> v-model:visible
okButtonProps、cancelButtonProps 扁平化处理 okButtonProps、cancelButtonProps 扁平化处理
## Mentions
v-model -> v-model:value

View File

@ -1,19 +1,16 @@
import { inject } from 'vue';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import Empty from '../empty'; import Empty from '../empty';
import { ConfigConsumerProps } from './'; import { ConfigConsumerProps } from './';
const RenderEmpty = { const RenderEmpty = {
functional: true,
inject: {
configProvider: { default: () => ConfigConsumerProps },
},
props: { props: {
componentName: PropTypes.string, componentName: PropTypes.string,
}, },
render(createElement, context) { setup(props) {
const { props, injections } = context; const configProvider = inject('configProvider', ConfigConsumerProps);
function renderHtml(componentName) { function renderHtml(componentName) {
const getPrefixCls = injections.configProvider.getPrefixCls; const getPrefixCls = configProvider.getPrefixCls;
const prefix = getPrefixCls('empty'); const prefix = getPrefixCls('empty');
switch (componentName) { switch (componentName) {
case 'Table': case 'Table':
@ -31,11 +28,11 @@ const RenderEmpty = {
return <Empty />; return <Empty />;
} }
} }
return renderHtml(props.componentName); return () => renderHtml(props.componentName);
}, },
}; };
function renderEmpty(h, componentName) { function renderEmpty(componentName) {
return <RenderEmpty componentName={componentName} />; return <RenderEmpty componentName={componentName} />;
} }

View File

@ -1,3 +1,4 @@
import { inject, nextTick } from 'vue';
import classNames from 'classnames'; import classNames from 'classnames';
import omit from 'omit.js'; import omit from 'omit.js';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
@ -7,12 +8,7 @@ import Base from '../base';
import Spin from '../spin'; import Spin from '../spin';
import BaseMixin from '../_util/BaseMixin'; import BaseMixin from '../_util/BaseMixin';
import { ConfigConsumerProps } from '../config-provider'; import { ConfigConsumerProps } from '../config-provider';
import { import { getOptionProps, getComponent, filterEmpty } from '../_util/props-util';
getOptionProps,
getComponentFromProp,
getListeners,
filterEmpty,
} from '../_util/props-util';
const { Option } = VcMentions; const { Option } = VcMentions;
@ -53,18 +49,16 @@ const Mentions = {
name: 'AMentions', name: 'AMentions',
mixins: [BaseMixin], mixins: [BaseMixin],
inheritAttrs: false, inheritAttrs: false,
model: {
prop: 'value',
event: 'change',
},
Option: { ...Option, name: 'AMentionsOption' }, Option: { ...Option, name: 'AMentionsOption' },
getMentions, getMentions,
props: { props: {
...mentionsProps, ...mentionsProps,
loading: PropTypes.bool, loading: PropTypes.bool,
}, },
inject: { setup() {
configProvider: { default: () => ConfigConsumerProps }, return {
configProvider: inject('configProvider', ConfigConsumerProps),
};
}, },
data() { data() {
return { return {
@ -72,7 +66,7 @@ const Mentions = {
}; };
}, },
mounted() { mounted() {
this.$nextTick(() => { nextTick(() => {
if (this.autoFocus) { if (this.autoFocus) {
this.focus(); this.focus();
} }
@ -99,19 +93,19 @@ const Mentions = {
}, },
onChange(val) { onChange(val) {
this.$emit('change', val); this.$emit('change', val);
this.$emit('update:value', val);
}, },
getNotFoundContent(renderEmpty) { getNotFoundContent(renderEmpty) {
const h = this.$createElement; const notFoundContent = getComponent(this, 'notFoundContent');
const notFoundContent = getComponentFromProp(this, 'notFoundContent');
if (notFoundContent !== undefined) { if (notFoundContent !== undefined) {
return notFoundContent; return notFoundContent;
} }
return renderEmpty(h, 'Select'); return renderEmpty('Select');
}, },
getOptions() { getOptions() {
const { loading } = this.$props; const { loading } = this.$props;
const children = filterEmpty(this.$slots.default || []); const children = filterEmpty(this.$slots.default?.() || []);
if (loading) { if (loading) {
return ( return (
@ -154,24 +148,20 @@ const Mentions = {
}); });
const mentionsProps = { const mentionsProps = {
props: { prefixCls,
prefixCls, notFoundContent: this.getNotFoundContent(renderEmpty),
notFoundContent: this.getNotFoundContent(renderEmpty), ...otherProps,
...otherProps, disabled,
disabled, filterOption: this.getFilterOption(),
filterOption: this.getFilterOption(), getPopupContainer,
getPopupContainer, children: this.getOptions(),
children: this.getOptions(),
},
class: mergedClassName, class: mergedClassName,
attrs: { rows: 1, ...this.$attrs }, rows: 1,
on: { ...this.$attrs,
...getListeners(this), onChange: this.onChange,
change: this.onChange, onSelect: this.onSelect,
select: this.onSelect, onFocus: this.onFocus,
focus: this.onFocus, onBlur: this.onBlur,
blur: this.onBlur,
},
ref: 'vcMentions', ref: 'vcMentions',
}; };
@ -180,10 +170,10 @@ const Mentions = {
}; };
/* istanbul ignore next */ /* istanbul ignore next */
Mentions.install = function(Vue) { Mentions.install = function(app) {
Vue.use(Base); app.use(Base);
Vue.component(Mentions.name, Mentions); app.component(Mentions.name, Mentions);
Vue.component(Mentions.Option.name, Mentions.Option); app.component(Mentions.Option.name, Mentions.Option);
}; };
export default Mentions; export default Mentions;

View File

@ -1,3 +1,4 @@
import { nextTick } from 'vue';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import { alignElement, alignPoint } from 'dom-align'; import { alignElement, alignPoint } from 'dom-align';
import addEventListener from '../vc-util/Dom/addEventListener'; import addEventListener from '../vc-util/Dom/addEventListener';
@ -30,7 +31,7 @@ export default {
return {}; return {};
}, },
mounted() { mounted() {
this.$nextTick(() => { nextTick(() => {
this.prevProps = { ...this.$props }; this.prevProps = { ...this.$props };
const props = this.$props; const props = this.$props;
// if parent ref not attached .... use document.getElementById // if parent ref not attached .... use document.getElementById
@ -41,7 +42,7 @@ export default {
}); });
}, },
updated() { updated() {
this.$nextTick(() => { nextTick(() => {
const prevProps = this.prevProps; const prevProps = this.prevProps;
const props = this.$props; const props = this.$props;
let reAlign = false; let reAlign = false;

View File

@ -1,6 +1,7 @@
import Menu, { MenuItem } from '../../vc-menu'; import Menu, { MenuItem } from '../../vc-menu';
import PropTypes from '../../_util/vue-types'; import PropTypes from '../../_util/vue-types';
import { OptionProps } from './Option'; import { OptionProps } from './Option';
import { inject } from 'vue';
function noop() {} function noop() {}
export default { export default {
@ -9,10 +10,11 @@ export default {
prefixCls: PropTypes.string, prefixCls: PropTypes.string,
options: PropTypes.arrayOf(OptionProps), options: PropTypes.arrayOf(OptionProps),
}, },
inject: { setup() {
mentionsContext: { default: {} }, return {
mentionsContext: inject('mentionsContext'),
};
}, },
render() { render() {
const { const {
notFoundContent, notFoundContent,
@ -27,37 +29,36 @@ export default {
return ( return (
<Menu <Menu
{...{ prefixCls={`${prefixCls}-menu`}
props: { activeKey={activeOption.value}
prefixCls: `${prefixCls}-menu`, onSelect={({ key }) => {
activeKey: activeOption.value, const option = options.find(({ value }) => value === key);
}, selectOption(option);
on: {
select: ({ key }) => {
const option = options.find(({ value }) => value === key);
selectOption(option);
},
focus: onFocus,
blur: onBlur,
},
}} }}
> onBlur={onBlur}
{options.map((option, index) => { onFocus={onFocus}
const { value, disabled, children } = option; children={[
return ( ...options.map((option, index) => {
<MenuItem const { value, disabled, children } = option;
key={value} return (
disabled={disabled} <MenuItem
onMouseenter={() => { key={value}
setActiveIndex(index); disabled={disabled}
}} onMouseenter={() => {
> setActiveIndex(index);
{children} }}
>
{children}
</MenuItem>
);
}),
!options.length && (
<MenuItem key="notFoundContent" disabled>
{notFoundContent}
</MenuItem> </MenuItem>
); ),
})} ].filter(Boolean)}
{!options.length && <MenuItem disabled>{notFoundContent}</MenuItem>} />
</Menu>
); );
}, },
}; };

View File

@ -49,7 +49,7 @@ export default {
const { $slots } = this; const { $slots } = this;
const children = $slots.default; const children = $slots.default?.();
const popupElement = this.getDropdownElement(); const popupElement = this.getDropdownElement();

View File

@ -1,13 +1,8 @@
import { provide, nextTick } from 'vue';
import omit from 'omit.js'; import omit from 'omit.js';
import KeyCode from '../../_util/KeyCode'; import KeyCode from '../../_util/KeyCode';
import BaseMixin from '../../_util/BaseMixin'; import BaseMixin from '../../_util/BaseMixin';
import { import { hasProp, getOptionProps, initDefaultProps } from '../../_util/props-util';
getSlots,
hasProp,
getOptionProps,
getListeners,
initDefaultProps,
} from '../../_util/props-util';
import warning from 'warning'; import warning from 'warning';
import { import {
getBeforeSelectionText, getBeforeSelectionText,
@ -24,15 +19,9 @@ const Mentions = {
name: 'Mentions', name: 'Mentions',
mixins: [BaseMixin], mixins: [BaseMixin],
inheritAttrs: false, inheritAttrs: false,
model: {
prop: 'value',
event: 'change',
},
props: initDefaultProps(vcMentionsProps, defaultProps), props: initDefaultProps(vcMentionsProps, defaultProps),
provide() { created() {
return { this.mentionsContext = provide('mentionsContext', this);
mentionsContext: this,
};
}, },
data() { data() {
const { value = '', defaultValue = '' } = this.$props; const { value = '', defaultValue = '' } = this.$props;
@ -53,7 +42,7 @@ const Mentions = {
}, },
}, },
updated() { updated() {
this.$nextTick(() => { nextTick(() => {
const { measuring } = this.$data; const { measuring } = this.$data;
// Sync measure div top with textarea for rc-trigger usage // Sync measure div top with textarea for rc-trigger usage
@ -215,8 +204,7 @@ const Mentions = {
const { filterOption, children = [] } = this.$props; const { filterOption, children = [] } = this.$props;
const list = (Array.isArray(children) ? children : [children]) const list = (Array.isArray(children) ? children : [children])
.map(item => { .map(item => {
const children = getSlots(item).default; return { ...getOptionProps(item), children: item.children.default?.() || item.children };
return { ...getOptionProps(item), children };
}) })
.filter(option => { .filter(option => {
/** Return all result if `filterOption` is false. */ /** Return all result if `filterOption` is false. */
@ -283,21 +271,17 @@ const Mentions = {
<textarea <textarea
ref="textarea" ref="textarea"
{...{ {...{
directives: [{ name: 'ant-input' }], ...inputProps,
attrs: { ...inputProps, ...this.$attrs }, ...this.$attrs,
domProps: { onChange: noop,
value, onSelect: noop,
},
on: {
...getListeners(this),
select: noop,
change: noop,
input: this.onChange,
keydown: this.onKeyDown,
keyup: this.onKeyUp,
blur: this.onInputBlur,
},
}} }}
value={value}
onInput={this.onChange}
onBlur={this.onInputBlur}
onKeyDown={this.onKeyDown}
onKeyUp={this.onKeyUp}
onFocus={this.onInputFocus}
/> />
{measuring && ( {measuring && (
<div ref="measure" class={`${prefixCls}-measure`}> <div ref="measure" class={`${prefixCls}-measure`}>

View File

@ -13,7 +13,6 @@ const Menu = {
selectable: PropTypes.bool.def(true), selectable: PropTypes.bool.def(true),
}, },
mixins: [BaseMixin], mixins: [BaseMixin],
data() { data() {
const props = getOptionProps(this); const props = getOptionProps(this);
let selectedKeys = props.defaultSelectedKeys; let selectedKeys = props.defaultSelectedKeys;

View File

@ -281,7 +281,7 @@ export default {
align={align} align={align}
onAlign={this.onAlign} onAlign={this.onAlign}
> >
<PopupInner {...popupInnerProps}>{$slots.default && $slots.default()}</PopupInner> <PopupInner {...popupInnerProps}>{$slots.default?.()}</PopupInner>
</Align> </Align>
</Transition> </Transition>
); );