diff --git a/examples/views/tag/TagTest.vue b/examples/views/tag/TagTest.vue index 702c3d3d..6f77dbc2 100644 --- a/examples/views/tag/TagTest.vue +++ b/examples/views/tag/TagTest.vue @@ -1,21 +1,119 @@ + diff --git a/package.json b/package.json index ddeb4d31..d0f6784b 100644 --- a/package.json +++ b/package.json @@ -36,11 +36,11 @@ "@vue/cli-plugin-typescript": "~5.0.0", "@vue/cli-plugin-vuex": "~5.0.0", "@vue/cli-service": "~5.0.0", - "@vue/eslint-config-standard": "^6.1.0", - "@vue/eslint-config-typescript": "^9.1.0", + "@vue/eslint-config-standard": "^7.0.0", + "@vue/eslint-config-typescript": "^10.0.0", "core-js": "^3.8.3", "del": "^6.1.1", - "eslint": "^7.32.0", + "eslint": "^8.57.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.1.1", diff --git a/packages/components.ts b/packages/components.ts index ff3c8452..875617b2 100644 --- a/packages/components.ts +++ b/packages/components.ts @@ -58,6 +58,7 @@ import VxeSwitch from './switch' import VxeTabPane from './tab-pane' import VxeTabs from './tabs' import VxeTag from './tag' +import VxeTags from './tags' import VxeText from './text' import VxeTextarea from './textarea' import VxeTip from './tip' @@ -125,6 +126,7 @@ const components = [ VxeTabPane, VxeTabs, VxeTag, + VxeTags, VxeText, VxeTextarea, VxeTip, diff --git a/packages/form-design/src/widget-info.ts b/packages/form-design/src/widget-info.ts index a1047919..097525e3 100644 --- a/packages/form-design/src/widget-info.ts +++ b/packages/form-design/src/widget-info.ts @@ -63,6 +63,7 @@ export class FormDesignWidgetInfo { name = '' required = false options: VxeFormPropTypes.Data = {} + // eslint-disable-next-line no-use-before-define children: FormDesignWidgetInfo[] = [] model = { update: false, diff --git a/packages/tag/index.ts b/packages/tag/index.ts index a7de045d..e50a31d2 100644 --- a/packages/tag/index.ts +++ b/packages/tag/index.ts @@ -8,7 +8,6 @@ export const VxeTag = Object.assign({}, VxeTagComponent, { app.component(VxeTagComponent.name as string, VxeTagComponent) } }) - dynamicApp.component(VxeTagComponent.name as string, VxeTagComponent) VxeUI.component(VxeTagComponent) diff --git a/packages/tag/src/tag.ts b/packages/tag/src/tag.ts index 32719cf7..fc0d5245 100644 --- a/packages/tag/src/tag.ts +++ b/packages/tag/src/tag.ts @@ -1,87 +1,200 @@ -import { defineComponent, ref, h, reactive, PropType, createCommentVNode } from 'vue' +import { defineComponent, ref, h, reactive, PropType, createCommentVNode, Ref, nextTick } from 'vue' import XEUtils from 'xe-utils' -import { getConfig, useSize } from '../../ui' import { getSlotVNs } from '../../ui/src/vn' -import type { VxeTagPropTypes, TagReactData, TagPrivateRef, VxeTagPrivateComputed, VxeTagConstructor, VxeTagPrivateMethods } from '../../../types' +import type { + VxeTagPropTypes, + TagReactData, + TagPrivateRef, + VxeTagPrivateComputed, + VxeTagConstructor, + VxeTagPrivateMethods, + VxeTagEmits, VxeTagMethods +} from '../../../types' +import { getFuncText } from '../../ui/src/utils' export default defineComponent({ name: 'VxeTag', props: { - status: String as PropType, - title: [String, Number] as PropType, - icon: String as PropType, - content: [String, Number] as PropType, - size: { type: String as PropType, default: () => getConfig().tag.size || getConfig().size } + content: [String, Number] as PropType, + color: { + type: String as PropType, + default: 'default' + }, + size: { + type: String as PropType, + default: 'medium' + }, + closable: { + type: Boolean as PropType, + default: false + }, + editable: { + type: Boolean as PropType, + default: false + }, + round: Boolean as PropType, + tagStyle: { + type: String as PropType, + default: 'default' + }, + icon: String as PropType, + iconSet: { + type: String as PropType, + default: '' + }, + align: { + type: String as PropType, + default: 'middle' + } }, - emits: [], + emits: [ + 'close', + 'update:content', + 'icon-click', + 'edit' + ] as VxeTagEmits, setup (props, context) { - const { slots } = context - + const { slots, emit } = context const xID = XEUtils.uniqueId() - - const { computeSize } = useSize(props) - - const refElem = ref() - + const tagStyleList = [ + 'default', 'outline', 'flag', 'dashed', 'mark', 'arrow' + ] const reactData = reactive({ + inited: false, + editing: false }) - - const refMaps: TagPrivateRef = { + const refElem = ref() as Ref + const refContent = ref() as Ref + const refMaps = { refElem } - - const computeMaps: VxeTagPrivateComputed = { + const closeTag = (event: Event) => new Promise(() => { + event.stopPropagation() + emit('close', { $event: { tag: $vxtag } }) + }) + const startEditing = () => new Promise(resolve => { + if (props.editable && !reactData.editing) { + reactData.editing = true + nextTick(() => { + refContent.value.focus() + const range = document.createRange() + range.selectNodeContents(refContent.value) + const selection = window.getSelection() + selection?.removeAllRanges() + selection?.addRange(range) + resolve(true) + }) + } else { + resolve(true) + } + }) + const handleContentEdited = () => { + if (props.editable) { + if (reactData.editing) { + emit('update:content', refContent.value.innerText) + emit('edit', refContent.value.innerText) + } + reactData.editing = false + } } - - const $xeTag = { + const handleIconClick = () => { + emit('icon-click', { $event: { tag: $vxtag } }) + } + let tagMethods = {} as VxeTagMethods + const $vxtag = { xID, props, context, reactData, - - getRefMaps: () => refMaps, - getComputeMaps: () => computeMaps - } as unknown as VxeTagConstructor & VxeTagPrivateMethods - - const renderContent = () => { - const { icon, content } = props - const defaultSlot = slots.default - const iconSlot = slots.icon - return [ - iconSlot || icon - ? h('span', { - class: 'vxe-tag--icon' - }, iconSlot - ? getSlotVNs(iconSlot({})) - : [ - h('i', { - class: icon - }) - ]) - : createCommentVNode(), - h('span', { - class: 'vxe-tag--content' - }, defaultSlot ? defaultSlot({}) : XEUtils.toValueString(content)) - ] + getRefMaps: () => refMaps + } as unknown as VxeTagConstructor + tagMethods = { + dispatchEvent (type, params, event) { + emit(type, Object.assign({ $tag: $vxtag, $event: event }, params)) + }, + close: closeTag, + startEditing } - + Object.assign($vxtag, tagMethods) + const renderContent = () => slots?.default?.() ?? getFuncText(props.content) + const renderIcon = () => slots?.icon?.() ?? (props.icon + ? h('i', { + class: [ + 'vxe-tag--icon', + props.iconSet, + props.icon + ], + onClick: handleIconClick + }) + : null) const renderVN = () => { - const { status, title } = props - const vSize = computeSize.value + const presetColors = [ + 'default', 'info', 'primary', 'success', 'warning', 'danger', 'error', 'perfect' + ] return h('span', { ref: refElem, - title, - class: ['vxe-tag', { - [`size--${vSize}`]: vSize, - [`theme--${status}`]: status - }] - }, renderContent()) + class: [ + 'vxe-tag', + props.editable && reactData.editing ? 'is--editing' : '', + props.closable ? 'vxe-tag--closable' : '', + `size--${props.size}`, + `vxe-tag-type--${tagStyleList.includes(props.tagStyle) ? props.tagStyle : 'default'}`, + props.round ? 'is--round' : '', + `vxe-tag-align--${props.align}`, + props.color + ? presetColors.includes(props.color) + ? `vxe-tag-color--${props.color}` + : '' + : 'vxe-tag-color--default', + { + closable: props.closable + } + ], + style: presetColors.includes(props.color) + ? null + : { + '--tag-color': props.color + } + }, + [ + props.closable + ? h('div', + { + class: 'vxe-tag-close-icon', + onClick: (event: Event) => { + // closeTag(event) + tagMethods.dispatchEvent('close', {}, event) + } + }, + [ + 'x' + ] + ) + : null, + slots?.avatar?.() ?? null, + h('span', { + class: ['vxe-tag-content', { 'tag-select-none': props.editable }], + ref: refContent, + contentEditable: props.editable && reactData.editing, + onClick: startEditing, + onBlur: handleContentEdited, + onKeydown: (event: KeyboardEvent) => { + if (event.key === 'Enter') { + handleContentEdited() + } else if (event.key === 'Escape') { + handleContentEdited() + } else if (event.key === 'Tab') { + handleContentEdited() + } + } + }, renderContent()), + renderIcon() + ] + ) } - - $xeTag.renderVN = renderVN - - return $xeTag + $vxtag.renderVN = renderVN + return $vxtag }, render () { return this.renderVN() diff --git a/packages/tags/index.ts b/packages/tags/index.ts new file mode 100644 index 00000000..80d9b821 --- /dev/null +++ b/packages/tags/index.ts @@ -0,0 +1,15 @@ +import { App } from 'vue' +import { VxeUI } from '@vxe-ui/core' +import VxeTagsComponent from './src/tags' +import { dynamicApp } from '../dynamics' + +export const VxeTags = Object.assign({}, VxeTagsComponent, { + install (app: App) { + app.component(VxeTagsComponent.name, VxeTagsComponent) + } +}) +dynamicApp.component(VxeTagsComponent.name, VxeTagsComponent) +VxeUI.component(VxeTagsComponent) + +export const Tags = VxeTags +export default VxeTags diff --git a/packages/tags/src/tags.ts b/packages/tags/src/tags.ts new file mode 100644 index 00000000..d7acb679 --- /dev/null +++ b/packages/tags/src/tags.ts @@ -0,0 +1,180 @@ +import { + ComponentOptions, + computed, + defineComponent, + h, + PropType, + reactive, Ref, + ref, + resolveComponent, unref +} from 'vue' +import { + TagsReactData, + VxeTagsEmits, + VxeTagsPrivateRef, + VxeTagsPropTypes +} from '../../../types/components/tags' +import { isFunction, isObject, isString, pick, uniqueId } from 'xe-utils' +import { VxeTagConstructor, VxeTagInstance, VxeTagProps } from '../../../types' + +export default defineComponent({ + name: 'vxeTags', + props: { + modelValue: { + type: Array as PropType, + default: () => [] + }, + color: { + type: String as PropType, + default: 'default' + }, + size: { + type: String as PropType, + default: 'medium' + }, + closable: { + type: Boolean as PropType, + default: false + }, + round: { + type: Boolean as PropType, + default: false + }, + tagStyle: { + type: String as PropType, + default: 'default' + }, + icon: { + type: String as PropType + }, + iconSet: { + type: String as PropType, + default: '' + }, + align: { + type: String as PropType, + default: 'middle' + }, + creator: { + type: [Function, Boolean] as PropType + } + }, + emits: [ + 'update:modelValue', + 'close', + 'edit', + 'icon-click', + 'tag-created' + ] as VxeTagsEmits, + setup (props, context) { + const { slots, emit } = context + const xID = uniqueId() + const reactData = reactive({ + inited: false, + innerTags: props.modelValue + }) + const refElem = ref() as Ref + const refTags = ref([]) + const activeTag = ref() as Ref + const refMaps: VxeTagsPrivateRef = { + refElem, + refTags, + activeTag + } + const $vxtags = { + xID, + props, + context, + reactData, + getRefMaps: () => refMaps + } as unknown as VxeTagConstructor + const parentProps = computed(() => { + const extendProps = pick({ ...props }, [ + 'color', + 'size', + 'closable', + 'editable', + 'round', + 'tagStyle', + 'icon', + 'iconSet', + 'align' + ]) + if (props.creator) { + extendProps.editable = true + } + return extendProps + }) + + const isSimple = computed(() => props.modelValue.every((item) => !isObject(item))) + const closeTag = (index: number) => { + const { innerTags } = reactData + innerTags.splice(index, 1) + emit('update:modelValue', innerTags) + } + const interleave = (arr: Array, x: any) => arr.flatMap((e: any) => [e, x]).slice(0, -1) + const renderTags = () => { + const { innerTags } = reactData + const tags = innerTags.map((item, index) => h(resolveComponent('vxe-tag') as ComponentOptions, { + key: uniqueId(), + ref: refTags.value[index], + onClose: () => closeTag(index), + onEdit: (value: string) => { + if (isString(innerTags[index])) { + innerTags[index] = value + } else { + (innerTags[index] as VxeTagProps).content = value + } + emit('update:modelValue', innerTags) + emit('edit', { $event: { index, tag: item } }) + }, + content: isSimple.value ? item : (item as VxeTagProps).content, + ...(isSimple.value ? parentProps.value : { ...parentProps.value, ...(item as VxeTagProps) }) + })) + const separator = renderSeparator() + return interleave(tags, separator) + } + const renderCreator = () => { + return h(resolveComponent('vxe-button') as ComponentOptions, { + icon: 'vxe-icon-square-plus-square', + type: 'text', + status: 'primary', + onClick: () => { + if (props.creator) { + const created = isFunction(props.creator) ? props.creator(props.modelValue) : '' + const tag = isSimple.value + ? isString(created) ? created : created?.content + : created === '' + ? { + ...unref(parentProps), + content: '' + } + : created + reactData.innerTags.push(tag) + emit('update:modelValue', reactData.innerTags) + emit('tag-created', { $event: { tag } }) + activeTag.value = refTags.value[refTags.value.length - 1] + /* activeTag 进入编辑状态 */ + activeTag.value?.startEditing() + } + } + }) + } + const renderSeparator = () => slots?.separator?.() ?? null + const renderVN = () => { + return h('span', { + ref: refElem, + class: ['vxe-tags-wrapper'] + }, [ + ...renderTags(), + props.creator ? renderCreator() : null + ]) + } + $vxtags.renderVN = renderVN + return $vxtags + }, + render () { + return this.renderVN() + } + +}) diff --git a/styles/components/tag.scss b/styles/components/tag.scss index a1e95c78..ff73e0fb 100644 --- a/styles/components/tag.scss +++ b/styles/components/tag.scss @@ -1,75 +1,187 @@ -$tagsThemeList: ( - ( - name: "primary", - textColor: var(--vxe-ui-font-primary-color), - bgColor: var(--vxe-ui-font-primary-tinge-color) - ), - ( - name: "success", - textColor: var(--vxe-ui-status-success-color), - bgColor: var(--vxe-ui-status-success-tinge-color) - ), - ( - name: "info", - textColor: var(--vxe-ui-status-info-color), - bgColor: var(--vxe-ui-status-info-tinge-color) - ), - ( - name: "warning", - textColor: var(--vxe-ui-status-warning-color), - bgColor: var(--vxe-ui-status-warning-tinge-color) - ), - ( - name: "danger", - textColor: var(--vxe-ui-status-danger-color), - bgColor: var(--vxe-ui-status-danger-tinge-color) - ), - ( - name: "error", - textColor: var(--vxe-ui-status-error-color), - bgColor: var(--vxe-ui-status-error-tinge-color) - ) -); +@mixin TagToRight { + position: relative; + border-radius: 4px; + background-color: var(--tag-color); + color: #fff; + padding-left: 1.2em; + + &:before { + background: #fff; + border-radius: 50%; + box-shadow: inset 0 1px rgba(0, 0, 0, 0.25); + content: ''; + height: 0.45em; + left: 0.5em; + position: absolute; + width: 0.45em; + top: calc(50% - 0.225em); + } +} .vxe-tag { - padding: var(--vxe-ui-layout-padding-half) var(--vxe-ui-layout-padding-default); - color: var(--vxe-ui-font-color); - border-radius: var(--vxe-ui-base-border-radius); - @for $index from 0 to length($tagsThemeList) { - $item: nth($tagsThemeList, $index + 1); - &.theme--#{map-get($item, name)} { - color: map-get($item, textColor); - background-color: map-get($item, bgColor); + padding: var(--vxe-ui-tag-padding); + position: relative; + width: auto; + display: inline-block; + + &.vxe-tag-type--default { + color: #fff; + background-color: var(--tag-color); + border-radius: var(--vxe-ui-tag-border-radius); + + &.is--round { + border-radius: 1em; } } - &.size--medium { - font-size: var(--vxe-ui-font-size-medium); + + &.vxe-tag-type--outline { + color: var(--tag-color); + background-color: transparent; + border: 1px solid var(--tag-color); + border-radius: var(--vxe-ui-tag-border-radius); + + &.is--round { + border-radius: 1em; + } } - &.size--small { - font-size: var(--vxe-ui-font-size-small); + + &.vxe-tag-type--dashed { + color: var(--tag-color); + background-color: transparent; + border: 1px dashed var(--tag-color); + border-radius: var(--vxe-ui-tag-border-radius); + + &.is--round { + border-radius: 1em; + } + } + + &.vxe-tag-type--mark { + @include TagToRight; + clip-path: polygon(100% 0%, 100% 100%, 0.5em 100%, 0% 50%, 0.5em 0%); + + &.is--round { + border-radius: 1em; + } + } + + &.vxe-tag-type--arrow { + @include TagToRight; + padding-right: 0.85em; + clip-path: polygon(0% 0%, calc(100% - 0.5em) 0%, 100% 50%, calc(100% - 0.5em) 100%, 0% 100%); + + &.is--round { + border-radius: 1em; + } + } + + &.vxe-tag-type--flag { + @include TagToRight; + padding-right: 1em; + clip-path: polygon(100% 0%, calc(100% - 0.5em) 50%, 100% 100%, 0.5em 100%, 0% 50%, 0.5em 0%); + + &.is--round { + border-radius: 1em; + } } + &.size--mini { - font-size: var(--vxe-ui-font-size-mini); + font-size: var(--vxe-ui-tag-font-size-mini); + line-height: var(--vxe-ui-tag-line-height--mini); } - & + .vxe-tag { - margin-left: 8px; + + &.size--small { + font-size: var(--vxe-ui-tag-font-size-small); + line-height: var(--vxe-ui-tag-line-height--small); } -} -.vxe-tag--icon { - padding: 0 0.1em; -} -.vxe-tag--content { - padding: 0 0.1em; -} - -.vxe-tag { + &.size--medium { - font-size: var(--vxe-ui-font-size-medium); + font-size: var(--vxe-ui-tag-font-size-medium); + line-height: var(--vxe-ui-tag-line-height--medium); } - &.size--small { - font-size: var(--vxe-ui-font-size-small); + + &.size--large { + font-size: var(--vxe-ui-tag-font-size-large); + line-height: var(--vxe-ui-tag-line-height--large); } - &.size--mini { - font-size: var(--vxe-ui-font-size-mini); + + &.vxe-tag--closable { + & > .vxe-tag-close-icon { + display: none; + } + + &:hover { + filter: brightness(1.1); + + & > .vxe-tag-close-icon { + display: inline-block; + position: absolute; + bottom: calc(50% - 0.5em); + left: calc(50% - 0.5em); + width: 1em; + height: 1em; + line-height: 1; + text-align: center; + border-radius: 50%; + background-color: #fff; + color: var(--tag-color); + } + } + } + + .vxe-tag--icon { + margin-left: 4px; + font-size: 11px; } -} \ No newline at end of file + + & > .vxe-tag-content { + outline: unset; + min-width: 1em; + min-height: 1em; + display: inline-block; + + &.tag-select-none { + user-select: none; + } + } +} + +.vxe-tag-color--default, .vxe-tag-color--info { + --tag-color: var(--vxe-ui-tag-default-color) +} + +.vxe-tag-color--primary { + --tag-color: var(--vxe-ui-status-primary-color) +} + +.vxe-tag-color--success { + --tag-color: var(--vxe-ui-status-success-color) +} + +.vxe-tag-color--warning { + --tag-color: var(--vxe-ui-status-warning-color) +} + +.vxe-tag-color--danger, .vxe-tag-color--error { + --tag-color: var(--vxe-ui-status-danger-color) +} + +.vxe-tag-color--perfect { + --tag-color: var(--vxe-ui-status-perfect-color) +} + +.vxe-tag + .vxe-tag { + margin-left: 0.4em; +} + +.vxe-tag-align--top { + vertical-align: top; +} + +.vxe-tag-align--middle { + vertical-align: middle; +} + +.vxe-tag-align--bottom { + vertical-align: bottom; +} diff --git a/styles/theme/base.scss b/styles/theme/base.scss index ae0e08e6..f76888bf 100644 --- a/styles/theme/base.scss +++ b/styles/theme/base.scss @@ -109,7 +109,7 @@ --vxe-ui-checkbox-checked-height: 0.64em; --vxe-ui-checkbox-indeterminate-width: 0.6em; --vxe-ui-checkbox-indeterminate-height: 2px; - --vxe-ui-checkbox-border-width: 2px; + --vxe-ui-checkbox-border-width: 2px; --vxe-ui-checkbox-border-radius: 2px; --vxe-ui-checkbox-icon-background-color: #fff; --vxe-ui-checkbox-checked-icon-border-color: #fff; @@ -147,10 +147,23 @@ /*card*/ --vxe-ui-card-padding: 12px; + + /*tag*/ + --vxe-ui-tag-default-color: #bbbccc; + --vxe-ui-tag-padding: 0.1em 0.5em ; + --vxe-ui-tag-border-radius: 4px ; + --vxe-ui-tag-line-height--mini: 1.2 ; + --vxe-ui-tag-line-height--small: 1.35 ; + --vxe-ui-tag-line-height--medium: 1.6 ; + --vxe-ui-tag-line-height--large: 1.7 ; + --vxe-ui-tag-font-size-mini: 11px ; + --vxe-ui-tag-font-size-small: 13px ; + --vxe-ui-tag-font-size-medium: 14px; + --vxe-ui-tag-font-size-large: 15px; /*tree*/ --vxe-ui-tree-node-height: 2em; --vxe-ui-tree-node-line-color:#909399; --vxe-ui-tree-node-line-style: dotted; --vxe-ui-tree-node-line-width: 1px; -} \ No newline at end of file +} diff --git a/styles/theme/dark.scss b/styles/theme/dark.scss index 7545ccc0..937c1a02 100644 --- a/styles/theme/dark.scss +++ b/styles/theme/dark.scss @@ -28,11 +28,13 @@ --vxe-ui-font-primary-darken-color: #0d84ff; --vxe-ui-font-primary-disabled-color: #a6d2ff; + --vxe-ui-status-primary-color: var(--vxe-ui-font-primary-color); --vxe-ui-status-success-color: #67c23a; --vxe-ui-status-info-color: #909399; --vxe-ui-status-warning-color: #e6a23c; --vxe-ui-status-danger-color: #f56c6c; --vxe-ui-status-error-color: #f56c6c; + --vxe-ui-status-perfect-color:#934db7; --vxe-ui-status-success-tinge-color: #33412f; --vxe-ui-status-info-tinge-color: #38383b; diff --git a/styles/theme/light.scss b/styles/theme/light.scss index a2d57a66..bd50435b 100644 --- a/styles/theme/light.scss +++ b/styles/theme/light.scss @@ -25,12 +25,14 @@ --vxe-ui-font-primary-lighten-color: #73b8ff; --vxe-ui-font-primary-darken-color: #0d84ff; --vxe-ui-font-primary-disabled-color: #a6d2ff; - + + --vxe-ui-status-primary-color: var(--vxe-ui-font-primary-color); --vxe-ui-status-success-color: #67c23a; --vxe-ui-status-info-color: #909399; --vxe-ui-status-warning-color: #e6a23c; --vxe-ui-status-danger-color: #f56c6c; --vxe-ui-status-error-color: #f56c6c; + --vxe-ui-status-perfect-color:#934db7; --vxe-ui-status-success-tinge-color: #eef8e9; --vxe-ui-status-info-tinge-color: #f5f5f6; @@ -82,7 +84,7 @@ /*loading*/ --vxe-ui-loading-background-color: rgba(255, 255, 255, 0.5); - + /*form-design*/ --vxe-ui-form-design-widget-hover-background-color: var(--vxe-ui-base-hover-background-color); --vxe-ui-form-design-sub-widget-hover-background-color: rgba(0, 0, 0, 0.05); diff --git a/types/all.d.ts b/types/all.d.ts index 85ceb250..9c6ac20f 100644 --- a/types/all.d.ts +++ b/types/all.d.ts @@ -58,6 +58,7 @@ import VxeSwitch from './components/switch' import VxeTabPane from './components/tab-pane' import VxeTabs from './components/tabs' import VxeTag from './components/tag' +import VxeTags from './components/tags' import VxeText from './components/text' import VxeTextarea from './components/textarea' import VxeTip from './components/tip' @@ -133,6 +134,7 @@ interface AllComponents { VxeTabPane: typeof VxeTabPane VxeTabs: typeof VxeTabs VxeTag: typeof VxeTag + VxeTags: typeof VxeTags VxeText: typeof VxeText VxeTextarea: typeof VxeTextarea VxeTip: typeof VxeTip diff --git a/types/components/tag.d.ts b/types/components/tag.d.ts index 2b0d7870..65c20939 100644 --- a/types/components/tag.d.ts +++ b/types/components/tag.d.ts @@ -1,5 +1,13 @@ -import { RenderFunction, SetupContext, Ref, ComponentPublicInstance, DefineComponent } from 'vue' -import { defineVxeComponent, VxeComponentBaseOptions, VxeComponentEventParams, VxeComponentSizeType, ValueOf, VxeComponentStatusType } from '@vxe-ui/core' +import { + RenderFunction, + SetupContext, + Ref, + ComponentPublicInstance, + DefineComponent, + VNode, + VNodeArrayChildren +} from 'vue' +import { defineVxeComponent, VxeComponentBaseOptions, VxeComponentEventParams, ValueOf, VxeComponentStatusType } from '@vxe-ui/core' /* eslint-disable no-use-before-define,@typescript-eslint/ban-types */ @@ -23,19 +31,59 @@ export interface TagPrivateRef { export interface VxeTagPrivateRef extends TagPrivateRef { } export namespace VxeTagPropTypes { - export type Status = VxeComponentStatusType - export type Title = string | number - export type Icon = string - export type Content = string | number - export type Size = VxeComponentSizeType + export type content = string + export type color = 'info' | 'primary' | 'success' | 'warning' | 'danger' | 'error' | 'perfect' | string + export type closable = boolean + export type editable = boolean + export type round = boolean + export type tagStyle = 'default' | 'outline' | 'flag' | 'dashed' | 'mark' | 'arrow' + export type size = 'medium' | 'small' | 'mini' | 'large' + export type icon = string + export type iconSet = string + export type align = 'top' | 'middle' | 'bottom' } export type VxeTagProps = { - status?: VxeTagPropTypes.Status - title?: VxeTagPropTypes.Title - icon?: VxeTagPropTypes.Icon - content?: VxeTagPropTypes.Content - size?: VxeTagPropTypes.Size + /** + * 内容 + */ + content?: VxeTagPropTypes.content + /** + * 颜色 + */ + color?: VxeTagPropTypes.color + /** + * 尺寸 + */ + size?: VxeTagPropTypes.size + /** + * 是否可关闭 + */ + closable?: VxeTagPropTypes.closable + /** + * 是否可编辑 + */ + editable?: VxeTagPropTypes.editable + /** + * 是否圆角 + */ + round?: VxeTagPropTypes.round + /** + * 标签风格样式 + */ + tagStyle?: VxeTagPropTypes.tagStyle + /** + * 图标 + */ + icon?: VxeTagPropTypes.icon + /** + * 图标库类名 默认是vxe本身图标 + */ + iconSet?: VxeTagPropTypes.iconSet + /** + * 内容对齐方式 + */ + align?: VxeTagPropTypes.align } export interface TagPrivateComputed { @@ -43,10 +91,22 @@ export interface TagPrivateComputed { export interface VxeTagPrivateComputed extends TagPrivateComputed { } export interface TagReactData { + inited: boolean, + editing: boolean } export interface TagMethods { - dispatchEvent(type: ValueOf, params: Record, evnt: Event | null): void + dispatchEvent (type: ValueOf, params: any, evnt: Event): void + + /** + * 关闭 + */ + close (event: Event): Promise + + /** + * 开始编辑 + */ + startEditing (): Promise } export interface VxeTagMethods extends TagMethods { } @@ -54,29 +114,28 @@ export interface TagPrivateMethods { } export interface VxeTagPrivateMethods extends TagPrivateMethods { } export type VxeTagEmits = [ - 'click' + 'close', + 'update:content', + 'icon-click', + 'edit', ] export namespace VxeTagDefines { export interface TagEventParams extends VxeComponentEventParams { $tag: VxeTagConstructor } - - export interface ClickParams { - } - export interface ClickEventParams extends TagEventParams, ClickParams { } } export type VxeTagEventProps = { - onClick?: VxeTagEvents.Click + onClose?: VxeTagEvents.Close; + onIconClick?: VxeTagEvents.IconClick; } -export interface VxeTagListeners { - click?: VxeTagEvents.Click -} +export interface VxeTagListeners { } export namespace VxeTagEvents { - export type Click = (params: VxeTagDefines.ClickEventParams) => void + export type Close = (params: VxeTagDefines.CloseEventParams) => void; + export type IconClick = (params: VxeTagDefines.IconClickEventParams) => void; } export namespace VxeTagSlotTypes { @@ -84,8 +143,15 @@ export namespace VxeTagSlotTypes { } export interface VxeTagSlots { - default: (params: VxeTagSlotTypes.DefaultSlotParams) => any - icon: (params: VxeTagSlotTypes.DefaultSlotParams) => any + default: (params: VxeTagSlotTypes.DefaultSlotParams) => string | number | boolean | VNode | VNodeArrayChildren + /** + * 头像 + */ + avatar: () => string | VNode + /** + * 图标 + */ + icon: () => string | VNode } export const Tag: typeof VxeTag diff --git a/types/components/tags.d.ts b/types/components/tags.d.ts new file mode 100644 index 00000000..a2cafcb4 --- /dev/null +++ b/types/components/tags.d.ts @@ -0,0 +1,127 @@ +import { VxeTagInstance, VxeTagProps, VxeTagPropTypes } from './tag' +import { ComponentPublicInstance, Ref, RenderFunction, SetupContext, VNode } from 'vue' +import { ValueOf, VXEComponent, VxeComponentBase, VxeEvent } from './component' + +export namespace VxeTagsPropTypes { + export type modelValue = Array + export type color = VxeTagPropTypes.color + export type size = VxeTagPropTypes.size + export type closable = VxeTagPropTypes.closable + export type round = VxeTagPropTypes.round + export type tagStyle = VxeTagPropTypes.tagStyle + export type icon = VxeTagPropTypes.icon + export type iconSet = VxeTagPropTypes.iconSet + export type align = VxeTagPropTypes.align + export type creator = boolean | ((exist?: Array) => VxeTagProps) +} +export type VxeTagsProps = { + modelValue?: VxeTagsPropTypes.modelValue + color?: VxeTagsPropTypes.color + size?: VxeTagsPropTypes.size + closable?: VxeTagsPropTypes.closable + round?: VxeTagsPropTypes.round + tagStyle?: VxeTagsPropTypes.tagStyle + icon?: VxeTagsPropTypes.icon + iconSet?: VxeTagsPropTypes.iconSet + align?: VxeTagsPropTypes.align + creator?: VxeTagsPropTypes.creator +} + +export interface TagsReactData { + inited: boolean, + innerTags: Array +} + +export interface TagsPrivateRef { + refElem: Ref; + refTags: Ref; + activeTag: Ref; +} + +export interface VxeTagsPrivateRef extends TagsPrivateRef {} + +export type VxeTagsEmits = [ + 'update:modelValue', + 'close', + 'edit', + 'icon-click', + 'tag-created', +] + +export interface TagsMethods { + dispatchEvent (type: ValueOf, params: any, evnt: Event): void + + /** + * 关闭其中一个标签 + */ + close (index: number): Promise + + /** + * 创建标签 + */ + create (tag: VxeTagProps, index?: number): void +} + +export interface VxeTagsMethods extends TagsMethods {} + +export interface VxeTagsConstructor extends VxeComponentBase, VxeTagsMethods { + props: VxeTagsProps; + context: SetupContext + reactData: TagsReactData + + getRefMaps (): VxeTagsPrivateRef + + renderVN: RenderFunction +} + +export namespace VxeTagsDefines { + export interface TagsEventParams extends VxeEvent { + $tags: VxeTagsConstructor + } + + export interface CloseParams { + index: number + } + + export interface IconClickParams { + index: number + } + + export interface TagCreatedParams { + tag: string | VxeTagProps + } + + export interface EditParams { + index: number + content: string + } +} +export namespace VxeTagsEvents { + export type Close = (params: VxeTagsDefines.TagsEventParams & VxeTagsDefines.CloseParams) => void; + export type IconClick = (params: VxeTagsDefines.TagsEventParams & VxeTagsDefines.IconClickParams) => void; + export type TagCreated = (params: VxeTagsDefines.TagsEventParams & VxeTagsDefines.TagCreatedParams) => void; + export type Edit = (params: VxeTagsDefines.TagsEventParams & VxeTagsDefines.EditParams) => void; +} + +export interface VxeTagsEventProps { + onClose?: VxeTagsEvents.Close; + onIconClick?: VxeTagsEvents.IconClick; + onTagCreated?: VxeTagsEvents.TagCreated; + onEdit?: VxeTagsEvents.Edit; +} + +export interface VxeTagsSlots { + /** + * 间隔插槽 + */ + separator: () => string | VNode +} + +export type VxeTagsInstance = ComponentPublicInstance +export const VxeTags: VXEComponent + +/** + * 组件 - 标签集 + * @example import { VxeTags } from 'vxe-components' + */ +export const Tags: typeof VxeTags diff --git a/types/ui/global-config.d.ts b/types/ui/global-config.d.ts index 9a0f5d20..81913052 100644 --- a/types/ui/global-config.d.ts +++ b/types/ui/global-config.d.ts @@ -54,6 +54,7 @@ import { VxeSwitchProps } from '../components/switch' import { VxeTabPaneProps } from '../components/tab-pane' import { VxeTabsProps } from '../components/tabs' import { VxeTagProps } from '../components/tag' +import { VxeTagsProps } from '../components/tags' import { VxeTextProps } from '../components/text' import { VxeTextareaProps } from '../components/textarea' import { VxeTipProps } from '../components/tip' @@ -126,6 +127,7 @@ declare module '@vxe-ui/core' { tabPane?: VxeTabPaneProps tabs?: VxeTabsProps tag?: VxeTagProps + tags?: VxeTagsProps text?: VxeTextProps textarea?: VxeTextareaProps tip?: VxeTipProps