From 1584f07f0cf148f40f7652ad758eda47b618697a Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu <591449570@qq.com> Date: Mon, 24 Aug 2020 15:38:30 +0800 Subject: [PATCH] [WIP]feat/tooltip - Code done - Awaiting test cases --- packages/element-plus/index.ts | 4 +- packages/popper/doc/basic.vue | 54 ------- packages/popper/doc/index.stories.ts | 5 - packages/popper/src/index.vue | 19 ++- packages/tooltip/__tests__/tooltip.spec.ts | 15 ++ packages/tooltip/doc/basic.vue | 143 ++++++++++++++++++ packages/tooltip/doc/index.stories.ts | 5 + packages/tooltip/index.ts | 5 + packages/tooltip/package.json | 12 ++ packages/tooltip/src/index.vue | 164 +++++++++++++++++++++ packages/utils/constants.ts | 2 + packages/utils/types.ts | 2 + packages/utils/util.ts | 49 ++---- typings/vue-shim.d.ts | 4 + 14 files changed, 386 insertions(+), 97 deletions(-) delete mode 100644 packages/popper/doc/basic.vue delete mode 100644 packages/popper/doc/index.stories.ts create mode 100644 packages/tooltip/__tests__/tooltip.spec.ts create mode 100644 packages/tooltip/doc/basic.vue create mode 100644 packages/tooltip/doc/index.stories.ts create mode 100644 packages/tooltip/index.ts create mode 100644 packages/tooltip/package.json create mode 100644 packages/tooltip/src/index.vue create mode 100644 packages/utils/constants.ts diff --git a/packages/element-plus/index.ts b/packages/element-plus/index.ts index ecab3c436b5ff..0e4eb17e990b9 100644 --- a/packages/element-plus/index.ts +++ b/packages/element-plus/index.ts @@ -25,7 +25,7 @@ import ElSteps from '@element-plus/steps' import ElCollapse from '@element-plus/collapse' import ElPopper from '@element-plus/popper' import ElTabs from '@element-plus/tabs' - +import ElTooltip from '@element-plus/tooltip' export { ElAlert, @@ -53,6 +53,7 @@ export { ElRadio, ElCollapse, ElTabs, + ElTooltip, } export default function install(app: App): void { @@ -82,4 +83,5 @@ export default function install(app: App): void { ElCollapse(app) ElPopper(app) ElTabs(app) + ElTooltip(app) } diff --git a/packages/popper/doc/basic.vue b/packages/popper/doc/basic.vue deleted file mode 100644 index e9ebf28a384cb..0000000000000 --- a/packages/popper/doc/basic.vue +++ /dev/null @@ -1,54 +0,0 @@ - - - - - diff --git a/packages/popper/doc/index.stories.ts b/packages/popper/doc/index.stories.ts deleted file mode 100644 index 95eb0e203060f..0000000000000 --- a/packages/popper/doc/index.stories.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default { - title: 'Popper', -} - -export { default as BasicPopper } from './basic.vue' diff --git a/packages/popper/src/index.vue b/packages/popper/src/index.vue index 9b80eb82d4cae..021d9b512e5d0 100644 --- a/packages/popper/src/index.vue +++ b/packages/popper/src/index.vue @@ -22,6 +22,7 @@ import { createPopper } from '@popperjs/core' import { generateId } from '@element-plus/utils/util' import { on, off } from '@element-plus/utils/dom' import throwError from '@element-plus/utils/error' +import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants' import useModifier from './useModifier' @@ -73,6 +74,10 @@ export default defineComponent({ type: String, default: '', }, + class: { + type: String, + default: '', + }, closeDelay: { type: Number, default: 200, @@ -153,12 +158,18 @@ export default defineComponent({ type: String, default: '0', }, + modelValue: { + type: Boolean, + default: undefined, + validator: val => typeof val === 'boolean', + }, value: { type: Boolean, default: false, }, }, - setup(props, { slots }) { + emits: [UPDATE_MODEL_EVENT], + setup(props, { slots, emit }) { const popperRef = ref(null) const arrowRef = ref(null) const trigger = ref(null) @@ -294,6 +305,7 @@ export default defineComponent({ }) _trigger.setAttribute('aria-describedby', popperId.value) _trigger.setAttribute('tabindex', props.tabIndex) + _trigger.classList.add(props.class) on(_trigger, 'mouseenter', _show) on(_trigger, 'mouseleave', _hide) on(_trigger, 'focus', handleFocus) @@ -302,12 +314,15 @@ export default defineComponent({ watch( () => visible.value, - () => { + val => { if (popperInstance.value) { popperInstance.value.update() } else { initializePopper() } + if (props.manualMode) { + emit(UPDATE_MODEL_EVENT, val) + } }, ) diff --git a/packages/tooltip/__tests__/tooltip.spec.ts b/packages/tooltip/__tests__/tooltip.spec.ts new file mode 100644 index 0000000000000..1895318597932 --- /dev/null +++ b/packages/tooltip/__tests__/tooltip.spec.ts @@ -0,0 +1,15 @@ +import { mount } from '@vue/test-utils' +import Tooltip from '../src/index.vue' + +const AXIOM = 'Rem is the best girl' + +describe('Tooltip.vue', () => { + test('render test', () => { + const wrapper = mount(Tooltip, { + slots: { + default: AXIOM, + }, + }) + expect(wrapper.text()).toEqual(AXIOM) + }) +}) diff --git a/packages/tooltip/doc/basic.vue b/packages/tooltip/doc/basic.vue new file mode 100644 index 0000000000000..158e45690d8e9 --- /dev/null +++ b/packages/tooltip/doc/basic.vue @@ -0,0 +1,143 @@ + + diff --git a/packages/tooltip/doc/index.stories.ts b/packages/tooltip/doc/index.stories.ts new file mode 100644 index 0000000000000..e960345a2c1c8 --- /dev/null +++ b/packages/tooltip/doc/index.stories.ts @@ -0,0 +1,5 @@ +export default { + title: 'Tooltip', +} + +export { default as Basic } from './basic.vue' diff --git a/packages/tooltip/index.ts b/packages/tooltip/index.ts new file mode 100644 index 0000000000000..5d998221921c7 --- /dev/null +++ b/packages/tooltip/index.ts @@ -0,0 +1,5 @@ +import { App } from 'vue' +import Tooltip from './src/index.vue' +export default (app: App): void => { + app.component(Tooltip.name, Tooltip) +} diff --git a/packages/tooltip/package.json b/packages/tooltip/package.json new file mode 100644 index 0000000000000..b426c15409ec3 --- /dev/null +++ b/packages/tooltip/package.json @@ -0,0 +1,12 @@ +{ + "name": "@element-plus/tooltip", + "version": "0.0.0", + "main": "dist/index.js", + "license": "MIT", + "peerDependencies": { + "vue": "^3.0.0-rc.6" + }, + "devDependencies": { + "@vue/test-utils": "^2.0.0-beta.0" + } +} diff --git a/packages/tooltip/src/index.vue b/packages/tooltip/src/index.vue new file mode 100644 index 0000000000000..b47249bb575cd --- /dev/null +++ b/packages/tooltip/src/index.vue @@ -0,0 +1,164 @@ + diff --git a/packages/utils/constants.ts b/packages/utils/constants.ts new file mode 100644 index 0000000000000..cae38cdf91709 --- /dev/null +++ b/packages/utils/constants.ts @@ -0,0 +1,2 @@ +export const UPDATE_MODEL_EVENT = 'update:modelValue' + diff --git a/packages/utils/types.ts b/packages/utils/types.ts index 260ad739b781d..e828f656cc3ea 100644 --- a/packages/utils/types.ts +++ b/packages/utils/types.ts @@ -12,3 +12,5 @@ type BiArgEmitter = (evt: K, arg: T[K]) export type EventEmitter> = MonoArgEmitter> & BiArgEmitter> + +export type AnyFunction = (...args: any[]) => T diff --git a/packages/utils/util.ts b/packages/utils/util.ts index 1a45c23831e27..e6339af4d4159 100644 --- a/packages/utils/util.ts +++ b/packages/utils/util.ts @@ -1,19 +1,15 @@ import isServer from './isServer' -import { isObject, capitalize } from '@vue/shared' +import { isObject, capitalize, hyphenate, looseEqual, extend } from '@vue/shared' import { isEmpty, castArray, isEqual } from 'lodash' -const { hasOwnProperty } = Object.prototype +import type { AnyFunction } from './types' -type Any = Record | unknown +const { hasOwnProperty } = Object.prototype -export function hasOwn(obj: Any, key: string): boolean { +export function hasOwn(obj: any, key: string): boolean { return hasOwnProperty.call(obj, key) } -function extend(to: T, _from: K): T & K { - return Object.assign(to, _from) -} - export function toObject(arr: Array): Record { const res = {} for (let i = 0; i < arr.length; i++) { @@ -24,7 +20,7 @@ export function toObject(arr: Array): Record { return res } -export const getValueByPath = (obj: Any, paths = ''): unknown => { +export const getValueByPath = (obj: any, paths = ''): unknown => { let ret: unknown = obj paths.split('.').map(path => { ret = ret?.[path] @@ -32,13 +28,12 @@ export const getValueByPath = (obj: Any, paths = ''): unknown => { return ret } -export function getPropByPath(obj: Any, path: string, strict: boolean): { +export function getPropByPath(obj: any, path: string, strict: boolean): { o: unknown k: string v: Nullable } { - // we can't use any here, the only option here is unknown - let tempObj: unknown = obj + let tempObj = obj path = path.replace(/\[(\w+)\]/g, '.$1') path = path.replace(/^\./, '') @@ -68,8 +63,7 @@ export const generateId = (): number => Math.floor(Math.random() * 10000) // use isEqual instead // export const valueEquals - -export const escapeRegexpString = (value = ''): string=> +export const escapeRegexpString = (value = ''): string => String(value).replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') // Use native Array.find, Array.findIndex instead @@ -106,36 +100,21 @@ export const autoprefixer = function(style: CSSStyleDeclaration): CSSStyleDeclar return style } -export const kebabCase = function(str: string): string { - const hyphenateRE = /([^-])([A-Z])/g - return str - .replace(hyphenateRE, '$1-$2') - .replace(hyphenateRE, '$1-$2') - .toLowerCase() -} - -export const looseEqual = function(a: T, b: K): boolean { - const isObjectA = isObject(a) - const isObjectB = isObject(b) - if (isObjectA && isObjectB) { - return JSON.stringify(a) === JSON.stringify(b) - } else if (!isObjectA && !isObjectB) { - return String(a) === String(b) - } else { - return false - } -} +export const kebabCase = hyphenate // reexport from lodash export { isEmpty, isEqual, + isObject, capitalize, + looseEqual, + extend, } -export function rafThrottle(fn: (args: Record) => unknown): (...args: unknown[]) => unknown { +export function rafThrottle>(fn: T): AnyFunction { let locked = false - return function(...args) { + return function(...args: any[]) { if (locked) return locked = true window.requestAnimationFrame(() => { diff --git a/typings/vue-shim.d.ts b/typings/vue-shim.d.ts index 3efc4c2c30634..e1a296d1e4f03 100644 --- a/typings/vue-shim.d.ts +++ b/typings/vue-shim.d.ts @@ -14,3 +14,7 @@ declare type CustomizedHTMLElement = HTMLElement & T; declare type Indexable = { [key: string]: T }; + +import type { Ref } from 'vue' + +declare type DOMRef = Ref