From 69f2e05f3777ec2a0ca398e0c3de76e8b5d5155f Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Mon, 20 Sep 2021 21:05:55 +0800 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=B0=B4?= =?UTF-8?q?=E6=B3=A2=E7=BA=B9Ripple=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/ripple/index.ts | 14 ++ devui/ripple/src/index.ts | 45 +++++ devui/ripple/src/options.ts | 74 +++++++++ devui/ripple/src/ripple-directive.ts | 30 ++++ devui/ripple/src/types.ts | 68 ++++++++ .../src/utils/createcontainerelement.ts | 21 +++ devui/ripple/src/utils/createwaveelement.ts | 23 +++ .../src/utils/getdistancetofurthestcorner.ts | 14 ++ devui/ripple/src/utils/getrelativepointer.ts | 7 + devui/ripple/src/utils/hookkeys.ts | 16 ++ devui/ripple/src/utils/isvue3.ts | 10 ++ devui/ripple/src/utils/magnitude.ts | 6 + devui/ripple/src/utils/wave-count.ts | 23 +++ devui/ripple/src/v-wave.ts | 91 ++++++++++ docs/.vitepress/config/sidebar.ts | 3 +- docs/components/ripple/index.md | 155 ++++++++++++++++++ 16 files changed, 599 insertions(+), 1 deletion(-) create mode 100644 devui/ripple/index.ts create mode 100644 devui/ripple/src/index.ts create mode 100644 devui/ripple/src/options.ts create mode 100644 devui/ripple/src/ripple-directive.ts create mode 100644 devui/ripple/src/types.ts create mode 100644 devui/ripple/src/utils/createcontainerelement.ts create mode 100644 devui/ripple/src/utils/createwaveelement.ts create mode 100644 devui/ripple/src/utils/getdistancetofurthestcorner.ts create mode 100644 devui/ripple/src/utils/getrelativepointer.ts create mode 100644 devui/ripple/src/utils/hookkeys.ts create mode 100644 devui/ripple/src/utils/isvue3.ts create mode 100644 devui/ripple/src/utils/magnitude.ts create mode 100644 devui/ripple/src/utils/wave-count.ts create mode 100644 devui/ripple/src/v-wave.ts create mode 100644 docs/components/ripple/index.md diff --git a/devui/ripple/index.ts b/devui/ripple/index.ts new file mode 100644 index 00000000..255ea416 --- /dev/null +++ b/devui/ripple/index.ts @@ -0,0 +1,14 @@ +import type { App } from 'vue' +import RippleDirective from './src/ripple-directive' + +export { RippleDirective } + +export default { + title: 'Ripple 水波纹', + category: '通用', + status: undefined, // TODO: 组件若开发完成则填入"已完成",并删除该注释 + install(app: App): void { + + app.directive('Ripple', RippleDirective) + } +} diff --git a/devui/ripple/src/index.ts b/devui/ripple/src/index.ts new file mode 100644 index 00000000..0eda751d --- /dev/null +++ b/devui/ripple/src/index.ts @@ -0,0 +1,45 @@ +import { + DEFAULT_PLUGIN_OPTIONS, + IVWaveDirectiveOptions, + IVWavePluginOptions +} from './options' +import { getHooks } from './utils/hookkeys' +import { wave } from './v-wave' +import { App, Plugin } from 'vue' + +const optionMap = new WeakMap< + HTMLElement, + Partial | false +>() + +const VWave = { + install(app: App, globalUserOptions: Partial = {}) { + + if (this.installed) return + this.installed = true + + const globalOptions = { ...DEFAULT_PLUGIN_OPTIONS, ...globalUserOptions } + const hooks = getHooks(app) + app.directive(globalOptions.directive, { + [hooks.mounted](el: HTMLElement, { value }: any) { + optionMap.set(el, value ?? {}) + + el.addEventListener('pointerdown', (event) => { + const options = optionMap.get(el)! + + if (options === false) return + + wave(event, el, { + ...globalOptions, + ...options + }) + }) + }, + [hooks.updated](el: HTMLElement, { value }: any) { + optionMap.set(el, value ?? {}) + } + }) + } +} as Plugin & { installed: boolean; } + +export default VWave diff --git a/devui/ripple/src/options.ts b/devui/ripple/src/options.ts new file mode 100644 index 00000000..1414fa7b --- /dev/null +++ b/devui/ripple/src/options.ts @@ -0,0 +1,74 @@ +interface IVWaveDirectiveOptions { + /** + * + * @remarks + * Y* 你可以设置 ·currentColor· to 能够自动使用元素的文本颜色 + * + * @default + * 'currentColor' + */ + color: string + /** + * 第一次出现的透明度 + * + * @default + * 0.2 默认opacity 0.2 + */ + initialOpacity: number + /** + * 在透明度 结束的时候 stopped 的时候 我们设置透明度的大小 + * + * @default + * 0.1 + */ + finalOpacity: number + /** + * 动画持续事件 + * + * @default + * 0.4 + */ + duration: number + /** + * css 动画 从开始到结束 以相同的时间来执行动画 + * + * @default + * 'ease-out' + */ + easing: string + /** + * 取消延迟时间 + * + * @note + * 类似于 debounceTime + * @default + * 75 + */ + cancellationPeriod: number +} + +interface IVWavePluginOptions extends IVWaveDirectiveOptions { + /** + * 用于覆盖指令的名称 + * + * @remarks + * + * @example + * + * @default + * 默认指令 wave + */ + directive: string +} + +const DEFAULT_PLUGIN_OPTIONS: IVWavePluginOptions = { + directive: 'wave', + color: 'currentColor', + initialOpacity: 0.2, + finalOpacity: 0.1, + duration: 0.8, + easing: 'ease-out', + cancellationPeriod: 75 +} + +export { DEFAULT_PLUGIN_OPTIONS, IVWavePluginOptions, IVWaveDirectiveOptions } diff --git a/devui/ripple/src/ripple-directive.ts b/devui/ripple/src/ripple-directive.ts new file mode 100644 index 00000000..3cdb45d7 --- /dev/null +++ b/devui/ripple/src/ripple-directive.ts @@ -0,0 +1,30 @@ +// can export function. +import { + DEFAULT_PLUGIN_OPTIONS, + IVWaveDirectiveOptions +} from './options' +import { wave } from './v-wave' +const optionMap = new WeakMap< + HTMLElement, + Partial | false +>() +const globalOptions = { ...DEFAULT_PLUGIN_OPTIONS } +export default { + mounted(el: HTMLElement, { value }: any) { + optionMap.set(el, value ?? {}) + + el.addEventListener('pointerdown', (event) => { + const options = optionMap.get(el)! + + if (options === false) return + + wave(event, el, { + ...globalOptions, + ...options + }) + }) + }, + updated(el: HTMLElement, { value }: any) { + optionMap.set(el, value ?? {}) + } +} diff --git a/devui/ripple/src/types.ts b/devui/ripple/src/types.ts new file mode 100644 index 00000000..782c333d --- /dev/null +++ b/devui/ripple/src/types.ts @@ -0,0 +1,68 @@ +// 插件自动安装 兼容 vue2 vue3 默认通过 hook 安装 本次 插件 详见 ripple-directives.ts + +interface IRippleDirectiveOption { + // @remarks + /** + * 你可以设置 ·currentColor· to 能够自动使用元素的文本颜色 + * default currentColor : string + */ + // @default + color: string + /** + * the first appear in this ripple 第一次出现的透明度 + * + * @default + * 0.2 默认opacity 0.2 + */ + initialOpacity: number + /** + * 在透明度 结束的时候 stopped 的时候 我们设置透明度的大小 + * + * @default + * 0.1 + */ + finalOpacity: number + /** + * 动画持续事件 + * + * @default + * 0.4 + * + */ + duration: number + /** + * css 动画 从开始到结束 以相同的时间来执行动画 + * + * @default + * ‘ease-out’ + */ + easing: string + /** + * 取消延迟时间 + */ + cancellationPeriod: number +} +interface IRipplePluginOption extends IRippleDirectiveOption { + /** + * + * @remarks + * + * @example + * + * @default + * '默认指令wave + */ + directive: string +} + +const DEFAULT_PLUGIN_OPTIONS: IRipplePluginOption = { + directive: 'ripple', + color: 'currentColor', + initialOpacity: 0.2, + finalOpacity: 0.1, + duration: 0.4, + easing: 'ease-out', + cancellationPeriod: 75 +} + +export { DEFAULT_PLUGIN_OPTIONS, IRipplePluginOption, IRippleDirectiveOption } diff --git a/devui/ripple/src/utils/createcontainerelement.ts b/devui/ripple/src/utils/createcontainerelement.ts new file mode 100644 index 00000000..0f3f570c --- /dev/null +++ b/devui/ripple/src/utils/createcontainerelement.ts @@ -0,0 +1,21 @@ +export const createContainer = ({ + borderTopLeftRadius, + borderTopRightRadius, + borderBottomLeftRadius, + borderBottomRightRadius +}: CSSStyleDeclaration) => { + const waveContainer = document.createElement('div') + waveContainer.style.top = '0' + waveContainer.style.left = '0' + waveContainer.style.width = '100%' + waveContainer.style.height = '100%' + waveContainer.style.position = 'absolute' + waveContainer.style.borderRadius = `${borderTopLeftRadius} ${borderTopRightRadius} ${borderBottomRightRadius} ${borderBottomLeftRadius}` + waveContainer.style.overflow = 'hidden' + waveContainer.style.pointerEvents = 'none' + + // 兼容 ie 苹果 + waveContainer.style.webkitMaskImage = '-webkit-radial-gradient(white, black)' + + return waveContainer +} diff --git a/devui/ripple/src/utils/createwaveelement.ts b/devui/ripple/src/utils/createwaveelement.ts new file mode 100644 index 00000000..51fedc9e --- /dev/null +++ b/devui/ripple/src/utils/createwaveelement.ts @@ -0,0 +1,23 @@ +import { IVWaveDirectiveOptions } from '../options' + +export const createWaveElement = ( + x: number, + y: number, + size: number, + options: IVWaveDirectiveOptions +) => { + const waveElement = document.createElement('div') + + waveElement.style.position = 'absolute' + waveElement.style.width = `${size}px` + waveElement.style.height = `${size}px` + waveElement.style.top = `${y}px` + waveElement.style.left = `${x}px` + waveElement.style.background = options.color + waveElement.style.borderRadius = '50%' + waveElement.style.opacity = `${options.initialOpacity}` + waveElement.style.transform = `translate(-50%,-50%) scale(0)` + waveElement.style.transition = `transform ${options.duration}s ${options.easing}, opacity ${options.duration}s ${options.easing}` + + return waveElement +} diff --git a/devui/ripple/src/utils/getdistancetofurthestcorner.ts b/devui/ripple/src/utils/getdistancetofurthestcorner.ts new file mode 100644 index 00000000..57cd96f5 --- /dev/null +++ b/devui/ripple/src/utils/getdistancetofurthestcorner.ts @@ -0,0 +1,14 @@ +import { magnitude } from './magnitude' + +export function getDistanceToFurthestCorner( + x: number, + y: number, + { width, height }: DOMRect +) { + const topLeft = magnitude(x, y, 0, 0) + const topRight = magnitude(x, y, width, 0) + const bottomLeft = magnitude(x, y, 0, height) + const bottomRight = magnitude(x, y, width, height) + + return Math.max(topLeft, topRight, bottomLeft, bottomRight) +} diff --git a/devui/ripple/src/utils/getrelativepointer.ts b/devui/ripple/src/utils/getrelativepointer.ts new file mode 100644 index 00000000..44d75b81 --- /dev/null +++ b/devui/ripple/src/utils/getrelativepointer.ts @@ -0,0 +1,7 @@ +export const getRelativePointer = ( + { x, y }: PointerEvent, + { top, left }: DOMRect +) => ({ + x: x - left, + y: y - top +}) diff --git a/devui/ripple/src/utils/hookkeys.ts b/devui/ripple/src/utils/hookkeys.ts new file mode 100644 index 00000000..87a92a0f --- /dev/null +++ b/devui/ripple/src/utils/hookkeys.ts @@ -0,0 +1,16 @@ +import { App } from 'vue' +import { isVue3 } from './isVue3' + +const getHooks = (app: App) => { + return isVue3(app) + ? { + mounted: 'mounted', + updated: 'updated' + } + : { + mounted: 'inserted', + updated: 'componentUpdated' + } +} + +export { getHooks } diff --git a/devui/ripple/src/utils/isvue3.ts b/devui/ripple/src/utils/isvue3.ts new file mode 100644 index 00000000..7e1597ed --- /dev/null +++ b/devui/ripple/src/utils/isvue3.ts @@ -0,0 +1,10 @@ +import { App } from 'vue' + +interface Vue2 { + default: { + version: string + } +} + +const isVue3 = (app: Vue2 | App): app is App => 'config' in app && 'globalProperties' in app.config +export { isVue3 } diff --git a/devui/ripple/src/utils/magnitude.ts b/devui/ripple/src/utils/magnitude.ts new file mode 100644 index 00000000..6ea251b0 --- /dev/null +++ b/devui/ripple/src/utils/magnitude.ts @@ -0,0 +1,6 @@ +export function magnitude(x1: number, y1: number, x2: number, y2: number) { + const deltaX = x1 - x2 + const deltaY = y1 - y2 + + return Math.sqrt(deltaX * deltaX + deltaY * deltaY) +} diff --git a/devui/ripple/src/utils/wave-count.ts b/devui/ripple/src/utils/wave-count.ts new file mode 100644 index 00000000..17dddece --- /dev/null +++ b/devui/ripple/src/utils/wave-count.ts @@ -0,0 +1,23 @@ +const WAVE_COUNT = 'vWaveCountInternal' + +export function incrementWaveCount(el: HTMLElement) { + const count = getWaveCount(el) + setWaveCount(el, count + 1) +} + +export function decrementWaveCount(el: HTMLElement) { + const count = getWaveCount(el) + setWaveCount(el, count - 1) +} + +function setWaveCount(el: HTMLElement, count: number) { + el.dataset[WAVE_COUNT] = count.toString() +} + +export function getWaveCount(el: HTMLElement) { + return parseInt(el.dataset[WAVE_COUNT] ?? '0', 10) +} + +export function deleteWaveCount(el: HTMLElement) { + delete el.dataset[WAVE_COUNT] +} diff --git a/devui/ripple/src/v-wave.ts b/devui/ripple/src/v-wave.ts new file mode 100644 index 00000000..88188e8a --- /dev/null +++ b/devui/ripple/src/v-wave.ts @@ -0,0 +1,91 @@ +import { createContainer } from './utils/createcontainerelement' +import { createWaveElement } from './utils/createwaveelement' +import { getDistanceToFurthestCorner } from './utils/getdistancetofurthestcorner' +import { getRelativePointer } from './utils/getRelativePointer' +import { + decrementWaveCount, + deleteWaveCount, + getWaveCount, + incrementWaveCount +} from './utils/wave-count' +import { IVWaveDirectiveOptions } from './options' + +const wave = ( + event: PointerEvent, + el: HTMLElement, + options: IVWaveDirectiveOptions +) => { + const rect = el.getBoundingClientRect() + const computedStyles = window.getComputedStyle(el) + + const { x, y } = getRelativePointer(event, rect) + const size = 2.05 * getDistanceToFurthestCorner(x, y, rect) // 2.05 is magic, deal with it. + + const waveContainer = createContainer(computedStyles) + const waveEl = createWaveElement(x, y, size, options) + + incrementWaveCount(el) + + let originalPositionValue = '' + if (computedStyles.position === 'static') { + if (el.style.position) originalPositionValue = el.style.position + el.style.position = 'relative' + } + + waveContainer.appendChild(waveEl) + el.appendChild(waveContainer) + + let shouldDissolveWave = false + const releaseWave = (e?: any) => { + if (typeof e !== 'undefined') { + document.removeEventListener('pointerup', releaseWave) + document.removeEventListener('pointercancel', releaseWave) + } + + if (shouldDissolveWave) dissolveWave() + else shouldDissolveWave = true + } + + const dissolveWave = () => { + waveEl.style.transition = 'opacity 150ms linear' + waveEl.style.opacity = '0' + + setTimeout(() => { + waveContainer.remove() + + decrementWaveCount(el) + + if (getWaveCount(el) === 0) { + deleteWaveCount(el) + el.style.position = originalPositionValue + } + }, 150) + } + + document.addEventListener('pointerup', releaseWave) + document.addEventListener('pointercancel', releaseWave) + + const token = setTimeout(() => { + document.removeEventListener('pointercancel', cancelWave) + + requestAnimationFrame(() => { + waveEl.style.transform = `translate(-50%,-50%) scale(1)` + waveEl.style.opacity = `${options.finalOpacity}` + + setTimeout(() => releaseWave(), options.duration * 1000) + }) + }, options.cancellationPeriod) + + const cancelWave = () => { + clearTimeout(token) + + waveContainer.remove() + document.removeEventListener('pointerup', releaseWave) + document.removeEventListener('pointercancel', releaseWave) + document.removeEventListener('pointercancel', cancelWave) + } + + document.addEventListener('pointercancel', cancelWave) +} + +export { wave } diff --git a/docs/.vitepress/config/sidebar.ts b/docs/.vitepress/config/sidebar.ts index aca6bee6..f774a6bb 100644 --- a/docs/.vitepress/config/sidebar.ts +++ b/docs/.vitepress/config/sidebar.ts @@ -12,7 +12,8 @@ const sidebar = { { text: 'Search 搜索框', link: '/components/search/', status: '已完成' }, { text: 'Status 状态', link: '/components/status/', status: '已完成' }, { text: 'Sticky 便贴', link: '/components/sticky/' }, - { text: 'Overlay 遮罩层', link: '/components/overlay/'} + { text: 'Overlay 遮罩层', link: '/components/overlay/'}, + { text: 'Ripple 水波纹', link: '/components/ripple/'} ] }, { diff --git a/docs/components/ripple/index.md b/docs/components/ripple/index.md new file mode 100644 index 00000000..c8844f6c --- /dev/null +++ b/docs/components/ripple/index.md @@ -0,0 +1,155 @@ +# Ripple 水波纹指令 + +`v-ripple` 指令 用于用户动作交互场景, 可以应用于任何块级元素 + +### 使用 + +用户 可以在组件 或者 HTML 元素上任意使用 `v-ripple` 指令 使用基本的 `v-ripple` 指令, `v-ripple` 接收 一个对象 + +### + +
HTML元素 中使用 v-ripple
+ + +:::demo +```vue + + + + +``` +::: + +### 其他 + +### 自定义色彩 + +### + +您可以通过修改文本颜色来动态改变 Ripple 的颜色 默认 Ripple 颜色 跟随文本 颜色 + +### + +
  • {{ item.text }}
+ +### + +当然 我们也可以 自己定义我们 想要的颜色 + +### + +
  • {{ item.text }}
+ +### + +### 我们还可以应用 于 其他组件 +### 例如 Button + +:::demo +```vue + + + + +``` +::: +### 例如 Icon + +:::demo +```vue + + + + +``` +::: + + +### API + +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 | +| :---------: | :------: | :-------: | :----------------------- | --------------------------------- | --------- | +| color | `string` | #00050 | 可选,默认当前文本颜色 | || +| initialOpacity | `number` | 0.1 | 可选,初始交互效果透明度大小 | || +| finalOpacity | `number` | 0.1 | 可选,结束交互效果长按透明度大小 | || +| duration | `number` | 0.4s | 可选,持续时间 | || +| easing | `string` | ease-out | 可选,缓动动画 | || +| cancellationPeriod | `number` | 75ms | 可选,延迟debouceTime时间后调用 | || + + -- Gitee From 8edea086f09711cf62aa23ef2ce4f28e6684b417 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Wed, 22 Sep 2021 14:08:23 +0800 Subject: [PATCH 2/9] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9TS=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/ripple/src/index.ts | 45 ------------------- devui/ripple/src/options.ts | 8 +++- devui/ripple/src/ripple-directive.ts | 15 ++++--- .../src/utils/createcontainerelement.ts | 2 +- devui/ripple/src/utils/createwaveelement.ts | 2 +- .../src/utils/getdistancetofurthestcorner.ts | 2 +- devui/ripple/src/utils/magnitude.ts | 2 +- devui/ripple/src/utils/wave-count.ts | 2 +- 8 files changed, 20 insertions(+), 58 deletions(-) delete mode 100644 devui/ripple/src/index.ts diff --git a/devui/ripple/src/index.ts b/devui/ripple/src/index.ts deleted file mode 100644 index 0eda751d..00000000 --- a/devui/ripple/src/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - DEFAULT_PLUGIN_OPTIONS, - IVWaveDirectiveOptions, - IVWavePluginOptions -} from './options' -import { getHooks } from './utils/hookkeys' -import { wave } from './v-wave' -import { App, Plugin } from 'vue' - -const optionMap = new WeakMap< - HTMLElement, - Partial | false ->() - -const VWave = { - install(app: App, globalUserOptions: Partial = {}) { - - if (this.installed) return - this.installed = true - - const globalOptions = { ...DEFAULT_PLUGIN_OPTIONS, ...globalUserOptions } - const hooks = getHooks(app) - app.directive(globalOptions.directive, { - [hooks.mounted](el: HTMLElement, { value }: any) { - optionMap.set(el, value ?? {}) - - el.addEventListener('pointerdown', (event) => { - const options = optionMap.get(el)! - - if (options === false) return - - wave(event, el, { - ...globalOptions, - ...options - }) - }) - }, - [hooks.updated](el: HTMLElement, { value }: any) { - optionMap.set(el, value ?? {}) - } - }) - } -} as Plugin & { installed: boolean; } - -export default VWave diff --git a/devui/ripple/src/options.ts b/devui/ripple/src/options.ts index 1414fa7b..8288d4fe 100644 --- a/devui/ripple/src/options.ts +++ b/devui/ripple/src/options.ts @@ -61,6 +61,12 @@ interface IVWavePluginOptions extends IVWaveDirectiveOptions { directive: string } +// 给可预见值 value 添加类型 + +interface IRippleDirectiveOptionWithBinding { + value: IVWaveDirectiveOptions +} + const DEFAULT_PLUGIN_OPTIONS: IVWavePluginOptions = { directive: 'wave', color: 'currentColor', @@ -71,4 +77,4 @@ const DEFAULT_PLUGIN_OPTIONS: IVWavePluginOptions = { cancellationPeriod: 75 } -export { DEFAULT_PLUGIN_OPTIONS, IVWavePluginOptions, IVWaveDirectiveOptions } +export { DEFAULT_PLUGIN_OPTIONS, IVWavePluginOptions, IVWaveDirectiveOptions, IRippleDirectiveOptionWithBinding } diff --git a/devui/ripple/src/ripple-directive.ts b/devui/ripple/src/ripple-directive.ts index 3cdb45d7..7612cd91 100644 --- a/devui/ripple/src/ripple-directive.ts +++ b/devui/ripple/src/ripple-directive.ts @@ -1,7 +1,8 @@ -// can export function. +// can export function. 解构参数类型冗余 新定义insterface IRippleDirectiveOptionWithBinding import { DEFAULT_PLUGIN_OPTIONS, - IVWaveDirectiveOptions + IVWaveDirectiveOptions, + IRippleDirectiveOptionWithBinding } from './options' import { wave } from './v-wave' const optionMap = new WeakMap< @@ -10,11 +11,11 @@ const optionMap = new WeakMap< >() const globalOptions = { ...DEFAULT_PLUGIN_OPTIONS } export default { - mounted(el: HTMLElement, { value }: any) { - optionMap.set(el, value ?? {}) + mounted(el: HTMLElement, binding: IRippleDirectiveOptionWithBinding) { + optionMap.set(el, binding.value ?? {}) el.addEventListener('pointerdown', (event) => { - const options = optionMap.get(el)! + const options = optionMap.get(el) if (options === false) return @@ -24,7 +25,7 @@ export default { }) }) }, - updated(el: HTMLElement, { value }: any) { - optionMap.set(el, value ?? {}) + updated(el: HTMLElement, binding: IRippleDirectiveOptionWithBinding) { + optionMap.set(el, binding.value ?? {}) } } diff --git a/devui/ripple/src/utils/createcontainerelement.ts b/devui/ripple/src/utils/createcontainerelement.ts index 0f3f570c..39b09bff 100644 --- a/devui/ripple/src/utils/createcontainerelement.ts +++ b/devui/ripple/src/utils/createcontainerelement.ts @@ -3,7 +3,7 @@ export const createContainer = ({ borderTopRightRadius, borderBottomLeftRadius, borderBottomRightRadius -}: CSSStyleDeclaration) => { +}: CSSStyleDeclaration): HTMLElement => { const waveContainer = document.createElement('div') waveContainer.style.top = '0' waveContainer.style.left = '0' diff --git a/devui/ripple/src/utils/createwaveelement.ts b/devui/ripple/src/utils/createwaveelement.ts index 51fedc9e..824b04b0 100644 --- a/devui/ripple/src/utils/createwaveelement.ts +++ b/devui/ripple/src/utils/createwaveelement.ts @@ -5,7 +5,7 @@ export const createWaveElement = ( y: number, size: number, options: IVWaveDirectiveOptions -) => { +): HTMLElement => { const waveElement = document.createElement('div') waveElement.style.position = 'absolute' diff --git a/devui/ripple/src/utils/getdistancetofurthestcorner.ts b/devui/ripple/src/utils/getdistancetofurthestcorner.ts index 57cd96f5..0b66d97f 100644 --- a/devui/ripple/src/utils/getdistancetofurthestcorner.ts +++ b/devui/ripple/src/utils/getdistancetofurthestcorner.ts @@ -4,7 +4,7 @@ export function getDistanceToFurthestCorner( x: number, y: number, { width, height }: DOMRect -) { +): number { const topLeft = magnitude(x, y, 0, 0) const topRight = magnitude(x, y, width, 0) const bottomLeft = magnitude(x, y, 0, height) diff --git a/devui/ripple/src/utils/magnitude.ts b/devui/ripple/src/utils/magnitude.ts index 6ea251b0..ed220249 100644 --- a/devui/ripple/src/utils/magnitude.ts +++ b/devui/ripple/src/utils/magnitude.ts @@ -1,4 +1,4 @@ -export function magnitude(x1: number, y1: number, x2: number, y2: number) { +export function magnitude(x1: number, y1: number, x2: number, y2: number): number { const deltaX = x1 - x2 const deltaY = y1 - y2 diff --git a/devui/ripple/src/utils/wave-count.ts b/devui/ripple/src/utils/wave-count.ts index 17dddece..264d8d4f 100644 --- a/devui/ripple/src/utils/wave-count.ts +++ b/devui/ripple/src/utils/wave-count.ts @@ -14,7 +14,7 @@ function setWaveCount(el: HTMLElement, count: number) { el.dataset[WAVE_COUNT] = count.toString() } -export function getWaveCount(el: HTMLElement) { +export function getWaveCount(el: HTMLElement): number { return parseInt(el.dataset[WAVE_COUNT] ?? '0', 10) } -- Gitee From 41b2337f4af8d1def3cd5c395b1bcfb87235d24e Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Thu, 23 Sep 2021 20:04:33 +0800 Subject: [PATCH 3/9] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4sidebar=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.vitepress/config/sidebar.ts | 101 ------------------------------ 1 file changed, 101 deletions(-) delete mode 100644 docs/.vitepress/config/sidebar.ts diff --git a/docs/.vitepress/config/sidebar.ts b/docs/.vitepress/config/sidebar.ts deleted file mode 100644 index f774a6bb..00000000 --- a/docs/.vitepress/config/sidebar.ts +++ /dev/null @@ -1,101 +0,0 @@ -const sidebar = { - '/': [ - { text: '快速开始', link: '/' }, - { - text: '通用', - children: [ - { text: 'Button 按钮', link: '/components/button/', status: '已完成' }, - { text: 'Icon 图标', link: '/components/icon/', status: '已完成' }, - { text: 'DragDrop 拖拽', link: '/components/dragdrop/' }, - { text: 'Fullscreen 全屏', link: '/components/fullscreen/' }, - { text: 'Panel 面板', link: '/components/panel/', status: '已完成' }, - { text: 'Search 搜索框', link: '/components/search/', status: '已完成' }, - { text: 'Status 状态', link: '/components/status/', status: '已完成' }, - { text: 'Sticky 便贴', link: '/components/sticky/' }, - { text: 'Overlay 遮罩层', link: '/components/overlay/'}, - { text: 'Ripple 水波纹', link: '/components/ripple/'} - ] - }, - { - text: '导航', - children: [ - { text: 'Accordion 手风琴', link: '/components/accordion/' }, - { text: 'Anchor 锚点', link: '/components/anchor/' }, - { text: 'BackTop 回到顶部', link: '/components/back-top/' }, - { text: 'Breadcrumb 面包屑', link: '/components/breadcrumb/' }, - { text: 'Dropdown 下拉菜单', link: '/components/dropdown/' }, - { text: 'NavSprite 导航精灵', link: '/components/nav-sprite/' }, - { text: 'Pagination 分页', link: '/components/pagination/', status: '开发中' }, - { text: 'StepsGuide 操作指引', link: '/components/steps-guide/' }, - { text: 'Tabs 选项卡', link: '/components/tabs/', status: '已完成' }, - { text: 'Anchor 锚点', link: '/components/Anchor/' }, - ] - }, - { - text: '反馈', - children: [ - { text: 'Alert 警告', link: '/components/alert/', status: '已完成' }, - { text: 'Drawer 抽屉板', link: '/components/drawer/' }, - { text: 'Loading 加载提示', link: '/components/loading/', status: '已完成' }, - { text: 'Mention 提及', link: '/components/mention/' }, - { text: 'Modal 模态弹窗', link: '/components/modal/' }, - { text: 'Popover 悬浮提示', link: '/components/popover/', status: "开发中" }, - { text: 'ReadTip 阅读提示', link: '/components/read-tip/' }, - { text: 'Toast 全局通知', link: '/components/toast/', status: '已完成' }, - { text: 'Tooltip 提示', link: '/components/tooltip/' }, - ] - }, - { - text: '数据录入', - children: [ - { text: 'AutoComplete 自动补全', link: '/components/auto-complete/' }, - { text: 'Cascader 级联菜单', link: '/components/cascader/' }, - { text: 'CategorySearch 分类搜索', link: '/components/category-search/' }, - { text: 'Checkbox 复选框', link: '/components/checkbox/', status: '已完成' }, - { text: 'DatePicker 日期选择器', link: '/components/date-picker/', status: '开发中' }, - { text: 'DatePickerPro 日期选择器', link: '/components/date-picker-pro/' }, - { text: 'EditableSelect 可编辑下拉框', link: '/components/editable-select/' }, - { text: 'Form 表单', link: '/components/form/' }, - { text: 'Input 文本框', link: '/components/input/', status: '已完成' }, - { text: 'InputNumber 数字输入框', link: '/components/input-number/' }, - { text: 'MultiAutoComplete 多项自动补全', link: '/components/multi-auto-complete/' }, - { text: 'Radio 单选框', link: '/components/radio/', status: '已完成' }, - { text: 'Select 下拉选择框', link: '/components/select/', status: '开发中' }, - { text: 'Slider 滑块', link: '/components/slider/' }, - { text: 'Switch 开关', link: '/components/switch/', status: '已完成' }, - { text: 'TagInput 标签输入', link: '/components/tag-input/', status: '已完成' }, - { text: 'Textarea 多行文本框', link: '/components/textarea/' }, - { text: 'TimePicker 时间选择器', link: '/components/time-picker/' }, - { text: 'Transfer 穿梭框', link: '/components/transfer/' }, - { text: 'TreeSelect 树形选择框', link: '/components/tree-select/' }, - { text: 'Upload 上传', link: '/components/upload/', status: '开发中' }, - ] - }, - { - text: '数据展示', - children: [ - { text: 'Avatar 头像', link: '/components/avatar/', status: '已完成' }, - { text: 'Badge 徽标', link: '/components/badge/', status: '已完成' }, - { text: 'Card 卡片', link: '/components/card/', status: '已完成' }, - { text: 'Carousel 走马灯', link: '/components/carousel/', status: '已完成' }, - { text: 'DataTable 表格', link: '/components/data-table/' }, - { text: 'Gantt 甘特图', link: '/components/gantt/' }, - { text: 'ImagePreview 图片预览', link: '/components/image-preview/' }, - { text: 'Progress 进度条', link: '/components/progress/', status: '已完成' }, - { text: 'QuadrantDiagram 象限图', link: '/components/quadrant-diagram/' }, - { text: 'Rate 等级评估', link: '/components/rate/', status: '已完成' }, - { text: 'Tag 标签', link: '/components/tag/' }, - { text: 'Tree 树', link: '/components/tree/' }, - ] - }, - { - text: '布局', - children: [ - { text: 'Layout 布局', link: '/components/layout/' }, - { text: 'Splitter 分割器', link: '/components/splitter/' } - ] - }, - ] -} - -export default sidebar -- Gitee From 8046a2ceff9da2d85a2a4ccfd8f719a50bf0ed27 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Sun, 26 Sep 2021 19:20:28 +0800 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/editable-select/index.ts | 20 ++ .../src/components/option/index.tsx | 11 + .../src/editable-select-types.ts | 22 ++ .../editable-select/src/editable-select.scss | 106 ++++++ devui/editable-select/src/editable-select.tsx | 71 ++++ devui/editable-select/src/utils/index.ts | 18 + .../pagination/src/components/config-menu.tsx | 2 +- devui/shared/util/class.ts | 44 +++ devui/shared/util/set-style.ts | 26 ++ devui/slider/__tests__/slider.spec.ts | 56 +++- devui/slider/src/slider.scss | 4 +- devui/slider/src/slider.tsx | 9 +- devui/splitter/index.ts | 18 + devui/splitter/src/splitter-bar-type.tsx | 34 ++ devui/splitter/src/splitter-bar.scss | 242 ++++++++++++++ devui/splitter/src/splitter-bar.tsx | 197 +++++++++++ devui/splitter/src/splitter-pane-type.tsx | 68 ++++ devui/splitter/src/splitter-pane.scss | 23 ++ devui/splitter/src/splitter-pane.tsx | 134 ++++++++ devui/splitter/src/splitter-store.ts | 195 +++++++++++ devui/splitter/src/splitter-types.ts | 29 ++ devui/splitter/src/splitter.scss | 18 + devui/splitter/src/splitter.tsx | 64 ++++ devui/splitter/src/util/d-resize-directive.ts | 85 +++++ devui/style/core/_dropdown.scss | 1 + docs/.vitepress/config/sidebar.ts | 100 ++++++ docs/components/editable-select/index.md | 34 ++ docs/components/splitter/index.md | 308 ++++++++++++++++++ 28 files changed, 1935 insertions(+), 4 deletions(-) create mode 100644 devui/editable-select/index.ts create mode 100644 devui/editable-select/src/components/option/index.tsx create mode 100644 devui/editable-select/src/editable-select-types.ts create mode 100644 devui/editable-select/src/editable-select.scss create mode 100644 devui/editable-select/src/editable-select.tsx create mode 100644 devui/editable-select/src/utils/index.ts create mode 100644 devui/shared/util/class.ts create mode 100644 devui/shared/util/set-style.ts create mode 100644 devui/splitter/index.ts create mode 100644 devui/splitter/src/splitter-bar-type.tsx create mode 100644 devui/splitter/src/splitter-bar.scss create mode 100644 devui/splitter/src/splitter-bar.tsx create mode 100644 devui/splitter/src/splitter-pane-type.tsx create mode 100644 devui/splitter/src/splitter-pane.scss create mode 100644 devui/splitter/src/splitter-pane.tsx create mode 100644 devui/splitter/src/splitter-store.ts create mode 100644 devui/splitter/src/splitter-types.ts create mode 100644 devui/splitter/src/splitter.scss create mode 100644 devui/splitter/src/splitter.tsx create mode 100644 devui/splitter/src/util/d-resize-directive.ts create mode 100644 docs/.vitepress/config/sidebar.ts create mode 100644 docs/components/editable-select/index.md create mode 100644 docs/components/splitter/index.md diff --git a/devui/editable-select/index.ts b/devui/editable-select/index.ts new file mode 100644 index 00000000..7b053c0c --- /dev/null +++ b/devui/editable-select/index.ts @@ -0,0 +1,20 @@ +import type { App } from 'vue' +import EditableSelect from './src/editable-select' +import EditableSelectOption from './src/components/option' + +EditableSelect.install = function (app: App): void { + app.component(EditableSelect.name, EditableSelect) + app.component(EditableSelectOption.name, EditableSelectOption) + +} + +export { EditableSelect, EditableSelectOption } + +export default { + title: 'EditableSelect 可输入下拉选择框', + category: '数据录入', + status: undefined, // TODO: 组件若开发完成则填入"已完成",并删除该注释 + install(app: App): void { + app.use(EditableSelect as any) + } +} diff --git a/devui/editable-select/src/components/option/index.tsx b/devui/editable-select/src/components/option/index.tsx new file mode 100644 index 00000000..e717d158 --- /dev/null +++ b/devui/editable-select/src/components/option/index.tsx @@ -0,0 +1,11 @@ +import { defineComponent } from 'vue'; + +export default defineComponent({ + name: 'DEditableSelectOption', + setup(props, ctx) { + const defaultSlot = ctx.slots.default && ctx.slots.default(); + return () => { + return
  • {defaultSlot}
  • ; + }; + }, +}); diff --git a/devui/editable-select/src/editable-select-types.ts b/devui/editable-select/src/editable-select-types.ts new file mode 100644 index 00000000..4b35deb5 --- /dev/null +++ b/devui/editable-select/src/editable-select-types.ts @@ -0,0 +1,22 @@ +import type { ExtractPropTypes } from 'vue' + +export const editableSelectProps = { + /* test: { + type: Object as PropType<{ xxx: xxx }> + } */ + width: { + type: Number, + default: 450 + }, + appendToBody: { + type: Boolean, + default: true, + }, + maxHeight: { + type: Number, + default: 300 + } + +} as const + +export type EditableSelectProps = ExtractPropTypes diff --git a/devui/editable-select/src/editable-select.scss b/devui/editable-select/src/editable-select.scss new file mode 100644 index 00000000..0af1fe5e --- /dev/null +++ b/devui/editable-select/src/editable-select.scss @@ -0,0 +1,106 @@ +@import '../../style/theme/color'; +@import '../../style/core/animation'; + +.devui-select-chevron-icon { + display: inline-flex; + vertical-align: middle; + transition: + transform $devui-animation-duration-slow + $devui-animation-ease-in-out-smooth; +} + +.devui-select-open .devui-select-chevron-icon { + transform: rotate(180deg); +} + +.devui-select-chevron-icon svg path { + fill: $devui-text-weak; // TODO: Color-Question +} + +input::-ms-clear { + display: none; +} + +.devui-no-data-tip { + user-select: none; + cursor: not-allowed; +} + +.devui-form-control { + outline: none; + padding-right: 24px; +} + +.devui-form-group.devui-has-feedback > .devui-form-control-feedback { + line-height: 26px; +} +// 下拉部分 +.devui-dropdown-menu { + width: 100%; + display: block; +} + +.devui-dropdown-item { + cursor: pointer; + display: block; + width: 100%; + padding: 8px 12px; + clear: both; + border: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: 14px; +} + +.devui-dropdown-menu { + .devui-dropdown-item:not(.disabled) { + &.selected { + color: $devui-list-item-active-text; + background-color: $devui-list-item-active-bg; + } + } +} + +.devui-no-result-template, +.devui-is-searching-template { + display: block; + width: 100%; + padding: 8px 12px; + clear: both; + border: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: not-allowed; + background-color: $devui-disabled-bg; + color: $devui-disabled-text; + line-height: 14px; + + &:hover, + &:active, + &:hover:active { + background-color: $devui-unavailable; + } +} +// 选项disabled +.devui-dropdown-item.disabled, +.devui-dropdown-item.disabled:hover { + cursor: not-allowed; + color: $devui-disabled-text; +} + +ul.devui-list-unstyled { + margin: 0; + overflow-y: auto; + padding: 0; +} + +.devui-dropdown-bg { + background: $devui-list-item-hover-bg; +} + +.devui-popup-tips { + color: $devui-text-weak; + padding: 4px 12px; +} diff --git a/devui/editable-select/src/editable-select.tsx b/devui/editable-select/src/editable-select.tsx new file mode 100644 index 00000000..74d1f046 --- /dev/null +++ b/devui/editable-select/src/editable-select.tsx @@ -0,0 +1,71 @@ +import './editable-select.scss'; + +import { defineComponent, ref, reactive, renderSlot } from 'vue'; +import { + editableSelectProps, + EditableSelectProps, +} from './editable-select-types'; +import { Icon } from '../../icon'; + +export default defineComponent({ + name: 'DEditableSelect', + props: editableSelectProps, + emits: [], + setup(props: EditableSelectProps, ctx) { + const origin = ref(null); + const visible = ref(false); + const position = reactive({ + originX: 'left', + originY: 'bottom', + overlayX: 'left', + overlayY: 'top', + }); + + const toggleMenu = () => { + visible.value = !visible.value; + }; + + return () => { + return ( + <> +
    + + +
    + +
    +
    +
      + {renderSlot(ctx.slots, 'default')} +
    +
    +
    +
    + + ); + }; + }, +}); diff --git a/devui/editable-select/src/utils/index.ts b/devui/editable-select/src/utils/index.ts new file mode 100644 index 00000000..fb6e5e68 --- /dev/null +++ b/devui/editable-select/src/utils/index.ts @@ -0,0 +1,18 @@ +/** + * 动态获取class字符串 + * @param classStr 是一个字符串,固定的class名 + * @param classOpt 是一个对象,key表示class名,value为布尔值,true则添加,否则不添加 + * @returns 最终的class字符串 + */ +export function className( + classStr: string, + classOpt?: { [key: string]: boolean; } +): string { + let classname = classStr; + if (typeof classOpt === 'object') { + Object.keys(classOpt).forEach((key) => { + classOpt[key] && (classname += ` ${key}`); + }); + } + return classname; +} \ No newline at end of file diff --git a/devui/pagination/src/components/config-menu.tsx b/devui/pagination/src/components/config-menu.tsx index 837beefa..2f1f1f0b 100644 --- a/devui/pagination/src/components/config-menu.tsx +++ b/devui/pagination/src/components/config-menu.tsx @@ -61,7 +61,7 @@ export default defineComponent({
    {v}
    ) }) diff --git a/devui/shared/util/class.ts b/devui/shared/util/class.ts new file mode 100644 index 00000000..046f6ce6 --- /dev/null +++ b/devui/shared/util/class.ts @@ -0,0 +1,44 @@ +/** + * 判断 DOM 中的元素是否含有某个类 + * @param el 元素 + * @param className 类名 + * @returns + */ +export function hasClass(el: HTMLElement, className: string): boolean { + if (el.classList) { + return el.classList.contains(className); + } + const originClass = el.className; + return ` ${originClass} `.indexOf(` ${className} `) > -1; +} + +/** + * 向 DOM 中的元素添加一个类 + * @param el 元素 + * @param className 类名 + */ +export function addClass(el: HTMLElement, className: string): void { + if (el.classList) { + el.classList.add(className); + } else { + if (!hasClass(el, className)) { + el.className = `${el.className} ${className}`; + } + } +} + +/** + * 从 DOM 中的元素移除一个类 + * @param el 元素 + * @param className 类名 + */ +export function removeClass(el: HTMLElement, className: string): void { + if (el.classList) { + el.classList.remove(className); + } else { + if (hasClass(el, className)) { + const originClass = el.className; + el.className = ` ${originClass} `.replace(` ${className} `, ' '); + } + } +} diff --git a/devui/shared/util/set-style.ts b/devui/shared/util/set-style.ts new file mode 100644 index 00000000..8ce5065b --- /dev/null +++ b/devui/shared/util/set-style.ts @@ -0,0 +1,26 @@ +import type { CSSProperties } from 'vue'; + +/** + * 设置元素的样式,返回上一次的样式 + * @param element + * @param style + * @returns + */ +export function setStyle( + element: HTMLElement, + style: CSSProperties, +): CSSProperties { + const oldStyle: CSSProperties = {}; + + const styleKeys = Object.keys(style); + + styleKeys.forEach((key) => { + oldStyle[key] = element.style[key]; + }); + + styleKeys.forEach((key) => { + element.style[key] = style[key]; + }); + + return oldStyle; +} \ No newline at end of file diff --git a/devui/slider/__tests__/slider.spec.ts b/devui/slider/__tests__/slider.spec.ts index 9f9d6680..146a319f 100644 --- a/devui/slider/__tests__/slider.spec.ts +++ b/devui/slider/__tests__/slider.spec.ts @@ -1,2 +1,56 @@ import { mount } from '@vue/test-utils'; -import DSlider from '../src/slider' \ No newline at end of file +import { nextTick, ref } from 'vue'; +import DSlider from '../src/slider'; + +describe('d-slider', () => { + it('slider maxValue && minValue work', () => { + const wrapper = mount(DSlider, { + props: { + max: 50, + min: 2, + }, + }); + const max = wrapper.find('.devui-max_count'); + const min = wrapper.find('.devui-min_count'); + expect(min.text()).toBe('2'); + expect(max.text()).toBe('50'); + }); + it('slider v-model work', async () => { + const value = ref(5); + const wrapper = mount({ + components: { DSlider }, + template: ` + + `, + setup() { + return { + modelValue: value, + }; + }, + }); + const input = wrapper.find('input'); + expect(input.element.value).toBe('5'); + input.setValue(10); + await nextTick(); + expect(value.value).toBe(10); + }); + it('slider showInput work', () => { + const wrapper = mount(DSlider, { + props: { + showInput: true, + }, + }); + const dInput = wrapper.find('.devui-input__out-wrap'); + expect(dInput.exists()).toBeTruthy(); + }); + + it('slider disabled work', () => { + const wrapper = mount(DSlider, { + props: { + disabled: true, + }, + }); + const slider = wrapper.find('.devui-slider__runway'); + expect(slider.classes()).toContain('disabled'); + }); +}); diff --git a/devui/slider/src/slider.scss b/devui/slider/src/slider.scss index 998b18c0..756f164a 100644 --- a/devui/slider/src/slider.scss +++ b/devui/slider/src/slider.scss @@ -89,7 +89,8 @@ color: $devui-text; } - .devui-input__wrap { + .devui-input__out-wrap { + font-size: $devui-font-size; position: absolute; right: -60px; top: -12px; @@ -99,6 +100,7 @@ input { width: 40px; + text-align: center; } } } diff --git a/devui/slider/src/slider.tsx b/devui/slider/src/slider.tsx index 46bc6d70..35975ec4 100644 --- a/devui/slider/src/slider.tsx +++ b/devui/slider/src/slider.tsx @@ -22,7 +22,13 @@ export default defineComponent({ //当前的位置以百分比显示 const percentDispaly = ref(''); const renderShowInput = () => { - return props.showInput ? : ''; + return props.showInput ? ( +
    + +
    + ) : ( + '' + ); }; //当传入modelValue时用以定位button的位置 @@ -142,6 +148,7 @@ export default defineComponent({ const re = /^(?:[1-9]?\d|100)$/; if (re.test(`${inputValue.value}`)) { percentDispaly.value = ((inputValue.value - props.min) * 100) / (props.max - props.min) + '%'; + ctx.emit('update:modelValue', inputValue.value); } } } diff --git a/devui/splitter/index.ts b/devui/splitter/index.ts new file mode 100644 index 00000000..6c473534 --- /dev/null +++ b/devui/splitter/index.ts @@ -0,0 +1,18 @@ +import type { App } from 'vue' +import Splitter from './src/splitter' +import SplitterPane from './src/splitter-pane' + +Splitter.install = function (app: App): void { + app.component(Splitter.name, Splitter) + app.component(SplitterPane.name, SplitterPane) +} + +export { Splitter } + +export default { + title: 'Splitter 分割器', + category: '布局', + install(app: App) { + app.use(Splitter as any) + }, +} diff --git a/devui/splitter/src/splitter-bar-type.tsx b/devui/splitter/src/splitter-bar-type.tsx new file mode 100644 index 00000000..e7ac1e23 --- /dev/null +++ b/devui/splitter/src/splitter-bar-type.tsx @@ -0,0 +1,34 @@ +import { PropType, ExtractPropTypes } from 'vue'; +import { SplitterOrientation } from './splitter-types'; + + +export const splitterBarProps = { + /** + * 当前 pane 的索引 + */ + index: { + type: Number, + }, + /** + * 必选,指定 SplitterBar 的方向 + */ + orientation: { + type: String as PropType, + required: true, + }, + /** + * 分隔条大小 + */ + splitBarSize: { + type: String, + required: true, + }, + /** + * 是否显示展开/收缩按钮 + */ + showCollapseButton: { + type: Boolean, + }, +} as const; + +export type SplitterBarProps = ExtractPropTypes; diff --git a/devui/splitter/src/splitter-bar.scss b/devui/splitter/src/splitter-bar.scss new file mode 100644 index 00000000..f78bcb91 --- /dev/null +++ b/devui/splitter/src/splitter-bar.scss @@ -0,0 +1,242 @@ +@import '../../style/theme/color'; +@import '../../style/theme/corner'; + +.devui-splitter-bar { + background-color: $devui-dividing-line; + display: flex; + position: relative; + align-items: center; + justify-content: center; + flex-grow: 0; + flex-shrink: 0; + + .devui-collapse { + background-color: $devui-dividing-line; + position: absolute; + z-index: 15; + cursor: pointer; + + &::before, + &::after { + content: ''; + width: 10px; + height: 2px; + background: #ffffff; + display: block; + position: absolute; + } + + &:hover { + background-color: $devui-brand-hover; + } + } + + &-horizontal { + .devui-collapse { + width: 12px; + height: 30px; + + &.prev, + &.next { + &.hidden { + display: none; + } + } + + &.prev { + border-radius: + 0 $devui-border-radius-feedback + $devui-border-radius-feedback 0; + left: 100%; + + &::before, + &.collapsed::before { + top: 9px; + left: 1px; + } + + &::before { + transform: rotate(-70deg); + } + + &.collapsed::before { + transform: rotate(70deg); + } + + &::after, + &.collapsed::after { + top: 18px; + left: 1px; + } + + &::after { + transform: rotate(70deg); + } + + &.collapsed::after { + transform: rotate(-70deg); + } + } + + &.next { + border-radius: + $devui-border-radius-feedback 0 0 + $devui-border-radius-feedback; + right: 100%; + + &::before, + &.collapsed::before { + top: 9px; + right: 1px; + } + + &::before { + transform: rotate(70deg); + } + + &.collapsed::before { + transform: rotate(-70deg); + } + + &::after, + &.collapsed::after { + top: 18px; + right: 1px; + } + + &::after { + transform: rotate(-70deg); + } + + &.collapsed::after { + transform: rotate(70deg); + } + } + } + } + + &-vertical { + .devui-collapse { + height: 12px; + width: 30px; + + &.prev, + &.next { + &.hidden { + display: none; + } + } + + &.prev { + border-radius: + 0 0 $devui-border-radius-feedback + $devui-border-radius-feedback; + top: 100%; + + &::before, + &.collapsed::before { + bottom: 5px; + left: 5px; + } + + &::before { + transform: rotate(-20deg); + } + + &.collapsed::before { + transform: rotate(20deg); + } + + &::after, + &.collapsed::after { + bottom: 5px; + left: 14px; + } + + &::after { + transform: rotate(20deg); + } + + &.collapsed::after { + transform: rotate(-20deg); + } + } + + &.next { + border-radius: + $devui-border-radius-feedback + $devui-border-radius-feedback 0 0; + bottom: 100%; + + &::before, + &.collapsed::before { + top: 5px; + left: 5px; + } + + &::before { + transform: rotate(20deg); + } + + &.collapsed::before { + transform: rotate(-20deg); + top: 5px; + left: 5px; + } + + &::after, + &.collapsed::after { + top: 5px; + left: 14px; + } + + &::after { + transform: rotate(-20deg); + } + + &.collapsed::after { + transform: rotate(20deg); + } + } + } + } + + &-horizontal.resizable:not(.none-resizable), + &-vertical.resizable:not(.none-resizable) { + &:hover, + &:focus, + &:active { + background-color: $devui-brand-hover; + } + + &::after { + content: ''; + display: block; + position: absolute; + z-index: 10; + } + } + + &-horizontal.resizable { + // 修正IE浏览器,css伪元素中鼠标手型无效 + cursor: col-resize; + + &::after { + cursor: col-resize; + height: 100%; + width: 10px; + top: 0; + } + } + + &-vertical.resizable { + cursor: row-resize; + + &::after { + cursor: row-resize; + width: 100%; + height: 10px; + left: 0; + } + } +} diff --git a/devui/splitter/src/splitter-bar.tsx b/devui/splitter/src/splitter-bar.tsx new file mode 100644 index 00000000..b48aa196 --- /dev/null +++ b/devui/splitter/src/splitter-bar.tsx @@ -0,0 +1,197 @@ +import { + defineComponent, + ref, + watch, + nextTick, + reactive, + computed, + withDirectives, + onMounted, + inject, +} from 'vue' +import type { SplitterStore } from './splitter-store' +import { setStyle } from '../../shared/util/set-style' +import { addClass, removeClass } from '../../shared/util/class' +import dresize, { ResizeDirectiveProp } from './util/d-resize-directive' +import './splitter-bar.scss' +import { splitterBarProps, SplitterBarProps } from './splitter-bar-type' + +export default defineComponent({ + name: 'DSplitterBar', + props: splitterBarProps, + setup(props: SplitterBarProps) { + const store: SplitterStore = inject('splitterStore') + const state = reactive({ + wrapperClass: `devui-splitter-bar devui-splitter-bar-${props.orientation}`, + }) + const domRef = ref() + + watch( + () => props.splitBarSize, + (curSplitBarSize) => { + nextTick(() => { + const ele = domRef?.value + setStyle(ele, { flexBasis: curSplitBarSize }) + }) + }, + { immediate: true } + ) + + watch( + () => store.state.panes, + () => { + if (!store.isStaticBar(props.index)) { + state.wrapperClass += ' resizable' + } else { + // TODO 禁用的样式处理 + // console.log(666); + } + }, + { deep: true } + ) + + // 指令输入值 + const coordinate = { pageX: 0, pageY: 0, originalX: 0, originalY: 0 } + let initState + // TODO 待优化,如何像 angular rxjs 操作一样优雅 + const resizeProp: ResizeDirectiveProp = { + enableResize: true, + onPressEvent: function ({ originalEvent }): void { + originalEvent.stopPropagation() // 按下的时候,阻止事件冒泡 + if (!store.isResizable(props.index)) return + initState = store.dragState(props.index) + coordinate.originalX = originalEvent.pageX + coordinate.originalY = originalEvent.pageY + }, + onDragEvent: function ({ originalEvent }): void { + originalEvent.stopPropagation() // 移动的时候,阻止事件冒泡 + if (!store.isResizable(props.index)) return + coordinate.pageX = originalEvent.pageX + coordinate.pageY = originalEvent.pageY + let distance + if (props.orientation === 'vertical') { + distance = coordinate.pageY - coordinate.originalY + } else { + distance = coordinate.pageX - coordinate.originalX + } + store.setSize(initState, distance) + }, + onReleaseEvent: function ({ originalEvent }): void { + originalEvent.stopPropagation() // 释放的时候,阻止事件冒泡 + if (!store.isResizable(props.index)) return + coordinate.pageX = originalEvent.pageX + coordinate.pageY = originalEvent.pageY + let distance + if (props.orientation === 'vertical') { + distance = coordinate.pageY - coordinate.originalY + } else { + distance = coordinate.pageX - coordinate.originalX + } + store.setSize(initState, distance) + }, + } + + const queryPanes = (index, nearIndex) => { + const pane = store.getPane(index) + const nearPane = store.getPane(nearIndex) + return { + pane, + nearPane, + } + } + + // 根据当前状态生成收起按钮样式 + const generateCollapseClass = (pane, nearPane, showIcon) => { + // 是否允许收起 + const isCollapsible = pane?.component?.props?.collapsible && showIcon + // 当前收起状态 + const isCollapsed = pane?.component?.props?.collapsed + // 一个 pane 收起的时候,隐藏相邻 pane 的收起按钮 + const isNearPaneCollapsed = nearPane.collapsed + return { + 'devui-collapse': isCollapsible, + collapsed: isCollapsed, + hidden: isNearPaneCollapsed, + } + } + + // 计算前面板收起操作样式 + const prevClass = computed(() => { + const { pane, nearPane } = queryPanes(props.index, props.index + 1) + // TODO 提示文字 + + // 第一个面板或者其它面板折叠方向不是向后的, 显示操作按钮 + const showIcon = + pane?.component?.props?.collapseDirection !== 'after' || + props.index === 0 + return generateCollapseClass(pane, nearPane, showIcon) + }) + + // 计算相邻面板收起操作样式 + const nextClass = computed(() => { + const { pane, nearPane } = queryPanes(props.index + 1, props.index) + // TODO 提示文字 + + // 最后一个面板或者其它面板折叠方向不是向前的显示操作按钮 + const showIcon = + pane?.component?.props?.collapseDirection !== 'before' || + props.index + 1 === store.state.paneCount - 1 + return generateCollapseClass(pane, nearPane, showIcon) + }) + + // 切换是否允许拖拽,收起时不能拖拽 + const toggleResize = () => { + const { pane, nearPane } = queryPanes(props.index, props.index + 1) + const isCollapsed = + pane?.component?.props?.collapsed || + nearPane?.component?.props?.collapsed + if (isCollapsed) { + addClass(domRef.value, 'none-resizable') + } else { + removeClass(domRef.value, 'none-resizable') + } + } + + const handleCollapsePrePane = (lockStatus?: boolean) => { + store.tooglePane(props.index, props.index + 1, lockStatus) + toggleResize() + } + + const handleCollapseNextPane = (lockStatus?: boolean) => { + store.tooglePane(props.index + 1, props.index, lockStatus) + toggleResize() + } + + const initialCollapseStatus = () => { + handleCollapsePrePane(true) + handleCollapseNextPane(true) + } + + onMounted(() => { + initialCollapseStatus() + }) + + return () => { + return withDirectives( +
    + {props.showCollapseButton ? ( +
    { + handleCollapsePrePane() + }} + >
    + ) : null} +
    + {props.showCollapseButton ? ( +
    handleCollapseNextPane()} + >
    + ) : null} +
    , + [[dresize, resizeProp]] + ) + } + }, +}) diff --git a/devui/splitter/src/splitter-pane-type.tsx b/devui/splitter/src/splitter-pane-type.tsx new file mode 100644 index 00000000..b1773e9e --- /dev/null +++ b/devui/splitter/src/splitter-pane-type.tsx @@ -0,0 +1,68 @@ +import { ExtractPropTypes, PropType } from 'vue'; +import { CollapseDirection } from './splitter-types'; + +export const splitterPaneProps = { + /** + * 可选,指定 pane 宽度,设置像素值或者百分比 + * pane初始化大小 + */ + size: { + type: String, + }, + /** + * 可选,指定 pane 最小宽度,设置像素值或者百分比 + */ + minSize: { + type: String, + }, + /** + * 可选,指定 pane 最大宽度,设置像素值或者百分比 + */ + maxSize: { + type: String, + }, + /** + * 可选,指定 pane 是否可调整大小,会影响相邻 pane + */ + resizable: { + type: Boolean, + default: true, + }, + /** + * 可选,指定 pane 是否可折叠收起 + */ + collapsible: { + type: Boolean, + default: false, + }, + /** + * 可选,指定 pane 初始化是否收起,配合 collapsible 使用 + */ + collapsed: { + type: Boolean, + default: false, + }, + /** + * 非边缘面板折叠方向,before 只生成向前折叠的按钮,after 生成向后折叠按钮,both 生成两个 + */ + collapseDirection: { + type: String as PropType, + default: 'both', + }, + /** + * 可选,是否在 pane 进行折叠后收缩 pane 宽度而非收起 + */ + shrink: { + type: Boolean, + default: false, + }, + /** + * 可选,折叠后收缩的 pane 宽度 (单位:px) + */ + shrinkWidth: { + type: Number, + default: 36, + }, +} as const; + +export type SplitterPaneProps = ExtractPropTypes; diff --git a/devui/splitter/src/splitter-pane.scss b/devui/splitter/src/splitter-pane.scss new file mode 100644 index 00000000..c8cf7700 --- /dev/null +++ b/devui/splitter/src/splitter-pane.scss @@ -0,0 +1,23 @@ +.devui-splitter-pane { + position: relative; + flex: 1 1 auto; + display: block; + min-width: 0; + max-width: 100%; + min-height: 0; + max-height: 100%; + + &-fixed { + flex-grow: 0; + flex-shrink: 0; + } + + &-hidden { + flex: 0 !important; + overflow: hidden !important; + } + + &-grow { + flex-grow: 1 !important; + } +} diff --git a/devui/splitter/src/splitter-pane.tsx b/devui/splitter/src/splitter-pane.tsx new file mode 100644 index 00000000..c588a518 --- /dev/null +++ b/devui/splitter/src/splitter-pane.tsx @@ -0,0 +1,134 @@ +import { + defineComponent, + ref, + watch, + nextTick, + inject, + onMounted, + onUpdated, +} from 'vue'; +import { addClass, hasClass, removeClass } from '../../shared/util/class'; +import { setStyle } from '../../shared/util/set-style'; +import type { SplitterStore } from './splitter-store'; +import { splitterPaneProps, SplitterPaneProps } from './splitter-pane-type'; +import './splitter-pane.scss'; + +export default defineComponent({ + name: 'DSplitterPane', + props: splitterPaneProps, + emits: ['sizeChange', 'collapsedChange'], + setup(props: SplitterPaneProps, { slots, expose }) { + const store: SplitterStore = inject('splitterStore'); + const domRef = ref(); + const order = ref(); + watch( + () => order.value, + (order) => { + nextTick(() => { + const ele = domRef.value; + setStyle(ele, { order }); + }); + } + ); + + // pane 初始化大小 + const setSizeStyle = (curSize) => { + const ele = domRef.value; + ele.style.flexBasis = curSize; + const paneFixedClass = 'devui-splitter-pane-fixed'; + if (curSize) { + // 设置 flex-grow 和 flex-shrink + addClass(ele, paneFixedClass); + } else { + removeClass(ele, paneFixedClass); + } + }; + + watch( + () => props.size, + (newSize) => { + nextTick(() => { + setSizeStyle(newSize); + }); + }, + { immediate: true } + ); + + const orientation = inject('orientation'); + let initialSize = ''; // 记录初始化挂载传入的大小 + onMounted(() => { + initialSize = props.size; + store.setPanes({ panes: store.state.panes }); + }); + + onUpdated(() => { + store.setPanes({ panes: store.state.panes }); + }); + + // 获取当前 pane大小 + const getPaneSize = (): number => { + const el = domRef?.value; + if (orientation === 'vertical') { + return el.offsetHeight; + } else { + return el.offsetWidth; + } + }; + + const toggleCollapseClass = () => { + const paneHiddenClass = 'devui-splitter-pane-hidden'; + nextTick(() => { + const el = domRef.value; + if (!props.collapsed) { + removeClass(el, paneHiddenClass); + } else { + addClass(el, paneHiddenClass); + } + + if (props.collapsed && props.shrink) { + removeClass(el, paneHiddenClass); + setStyle(el, { flexBasis: `${props.shrinkWidth}px` }); + } else { + setStyle(el, { flexBasis: initialSize }); + } + }); + }; + + watch( + () => props.collapsed, + () => { + nextTick(() => { + toggleCollapseClass(); + }); + }, + { immediate: true } + ); + + // 收起时用于改变相邻 pane 的 flex-grow 属性来改变非自适应 pane 的 size + const toggleNearPaneFlexGrow = (collapsed) => { + nextTick(() => { + const flexGrowClass = 'devui-splitter-pane-grow'; + if (hasClass(domRef.value, flexGrowClass)) { + removeClass(domRef.value, flexGrowClass); + } else if (collapsed) { + addClass(domRef.value, flexGrowClass); + } + }); + }; + + // 暴露给外部使用 + expose({ + order, + getPaneSize, + toggleNearPaneFlexGrow, + }); + + return () => { + return ( +
    + {slots.default?.()} +
    + ); + }; + }, +}); diff --git a/devui/splitter/src/splitter-store.ts b/devui/splitter/src/splitter-store.ts new file mode 100644 index 00000000..50787194 --- /dev/null +++ b/devui/splitter/src/splitter-store.ts @@ -0,0 +1,195 @@ +import SplitterPane from './splitter-pane' +import { reactive } from 'vue' + +export interface Pane { + getPaneSize: () => number +} + +export interface PaneState { + index: number + initialSize: number + minSize: number + maxSize: number +} + +export interface DragState { + prev: PaneState + next: PaneState +} + +type SplitterPane = typeof SplitterPane & Pane +export interface splitterState { + panes: Array // 所有 pane 对象的一些关键信息 + paneCount: number + splitterContainerSize: number +} + +export class SplitterStore { + state: splitterState + constructor() { + this.state = reactive({ + panes: [], + splitterContainerSize: 0, + paneCount: 0, + }) + } + // 配置 pane 信息,panes 列表,方便后续计算使用 + setPanes({ panes }): void { + this.state.panes = panes.map((pane: SplitterPane, index: number) => { + if (pane.component) { + pane.component.exposed.order.value = index * 2 + } + pane.getPaneSize = pane?.component?.exposed.getPaneSize + return pane + }) + this.state.paneCount = panes.length + } + setSplitter({ containerSize }: { containerSize: number; }): void { + this.state.splitterContainerSize = containerSize + } + + // 获取 pane,防止没有初始化的时候调用内部方法取值 + getPane(index: number): SplitterPane { + if (!this.state.panes || index < 0 || index >= this.state.panes.length) { + throw new Error('no pane can return.') + } + return this.state.panes[index] + } + + // 按下的时候计算 pane 的 size 信息 + dragState(splitbarIndex: number): DragState { + const prev = this.getPane(splitbarIndex) + const next = this.getPane(splitbarIndex + 1) + const total = prev.getPaneSize() + next.getPaneSize() + return { + prev: { + index: splitbarIndex, + initialSize: prev.getPaneSize(), + // 设置有最小值,直接取值,如果没有设置就用两个 pane 总和减去相邻 pane 的最大值,都没设置(NaN)再取0 + minSize: + this.toPixels(prev.component.props.minSize) || + total - this.toPixels(next.component.props.maxSize) || + 0, + // 设置最大值,直接取值,如果没有设置就用两个 pane 总和减去相邻 pane 的最小值,都没设置(NaN)再取两个 pane 总和 + maxSize: + this.toPixels(prev.component.props.maxSize) || + total - this.toPixels(next.component.props.minSize) || + total, + }, + next: { + index: splitbarIndex + 1, + initialSize: next.getPaneSize(), + minSize: + this.toPixels(next.component.props.minSize) || + total - this.toPixels(prev.component.props.maxSize) || + 0, + maxSize: + this.toPixels(next.component.props.maxSize) || + total - this.toPixels(prev.component.props.minSize) || + total, + }, + } + } + + // 大小限制函数,(max)小于最小值时取最小值,(min)大于最大值时取最大值 + clamp(minSize: number, maxSize: number, initialSize: number): number { + return Math.min(maxSize, Math.max(minSize, initialSize)) + } + + // resize pane的大小 + resize(paneState: PaneState, moveSize: number): void { + const pane = this.getPane(paneState.index) + const splitterSize = this.state.splitterContainerSize + const newSize = this.clamp( + paneState.minSize, + paneState.maxSize, + paneState.initialSize + moveSize + ) + let size = '' + if (this.isPercent(pane.component.props.size)) { + size = (newSize / splitterSize) * 100 + '%' + } else { + size = newSize + 'px' + } + pane.component.props.size = size + pane.component.emit('sizeChange', size) + } + + // 判断 pane 是否可以调整大小,只要有一边设置了不可调整或者收起,相邻 pane 调整就失效 + isResizable(splitBarIndex: number): boolean { + const prevPane = this.getPane(splitBarIndex) + const nextPane = this.getPane(splitBarIndex + 1) + const paneCollapsed = + prevPane?.component?.props?.collapsed || + nextPane?.component?.props?.collapsed + return ( + prevPane?.component?.props?.resizable && + nextPane?.component?.props?.resizable && + !paneCollapsed + ) + } + + // 判断分割条是否是固定的,只要有一边不能调整, 就是禁用状态固定 bar + isStaticBar(splitBarIndex: number): boolean { + const prevPane = this.getPane(splitBarIndex) + const nextPane = this.getPane(splitBarIndex + 1) + return !( + prevPane?.component?.props?.resizable && + nextPane?.component?.props?.resizable + ) + } + + // 判断是不是百分比设置宽度 + isPercent(size: string) { + return /%$/.test(size) + } + + // 计算时把百分比转换为像素 + toPixels(size: string): number { + // 值不满足转换时,result 为 NaN,方便计算最小、最大宽度判断 + let result = parseFloat(size) + if (this.isPercent(size)) { + result = (this.state.splitterContainerSize * result) / 100 + } + return result + } + + // 切换 pane 展开,收起 + tooglePane( + paneIndex: number, + nearPaneIndex: number, + lockStatus?: boolean + ): void { + const pane = this.getPane(paneIndex) + const nearPane = this.getPane(nearPaneIndex) + if (pane?.component?.props?.collapsible) { + pane.component.props.collapsed = lockStatus + ? pane?.component?.props?.collapsed + : !pane?.component?.props?.collapsed + nearPane?.component?.exposed?.toggleNearPaneFlexGrow( + pane?.component?.props?.collapsed + ) + pane?.component?.emit( + 'collapsedChange', + pane?.component?.props?.collapsed + ) + } + } + + // 设置 pane 大小 + setSize(state: DragState, distance: number): void { + const prev = this.getPane(state.prev.index) + const next = this.getPane(state.next.index) + if (prev.component.props.size && next.component.props.size) { + // 相邻的两个 pane 都指定了 size,需要同时修改 size + this.resize(state.prev, distance) + this.resize(state.next, -distance) + } else if (next.component.props.size) { + // 只有 next pane指定了 size,直接修改 next pane + this.resize(state.next, -distance) + } else { + // 最后都没有指定 size,直接修改 pre pane + this.resize(state.prev, distance) + } + } +} diff --git a/devui/splitter/src/splitter-types.ts b/devui/splitter/src/splitter-types.ts new file mode 100644 index 00000000..73467eb8 --- /dev/null +++ b/devui/splitter/src/splitter-types.ts @@ -0,0 +1,29 @@ +import type { PropType, ExtractPropTypes } from 'vue'; +export type SplitterOrientation = 'vertical' | 'horizontal'; +export type CollapseDirection = 'before' | 'after' | 'both'; + +export const splitterProps = { + /** + * 可选,指定 Splitter 分割方向,可选值'vertical'|'horizontal' + */ + orientation: { + type: String as PropType, + default: 'horizontal', + }, + /** + * 可选,分隔条大小,默认 2px + */ + splitBarSize: { + type: String, + default: '2px', + }, + /** + * 是否显示展开/收缩按钮 + */ + showCollapseButton: { + type: Boolean, + default: true, + }, +} as const; + +export type SplitterProps = ExtractPropTypes; diff --git a/devui/splitter/src/splitter.scss b/devui/splitter/src/splitter.scss new file mode 100644 index 00000000..a2f5eb81 --- /dev/null +++ b/devui/splitter/src/splitter.scss @@ -0,0 +1,18 @@ +@import '../../style/theme/color'; +@import '../../style/theme/corner'; + +.devui-splitter { + display: flex; + width: 100%; + height: auto; + position: relative; + border-radius: $devui-border-radius; + + &.devui-splitter-horizontal { + flex-direction: row; + } + + &.devui-splitter-vertical { + flex-direction: column; + } +} diff --git a/devui/splitter/src/splitter.tsx b/devui/splitter/src/splitter.tsx new file mode 100644 index 00000000..e9afe95f --- /dev/null +++ b/devui/splitter/src/splitter.tsx @@ -0,0 +1,64 @@ +import { defineComponent, reactive, ref, provide, nextTick } from 'vue' +import { splitterProps, SplitterProps } from './splitter-types' +import DSplitterBar from './splitter-bar' +import { SplitterStore } from './splitter-store' +import './splitter.scss' + +export default defineComponent({ + name: 'DSplitter', + components: { + DSplitterBar, + }, + props: splitterProps, + emits: [], + setup(props: SplitterProps, ctx) { + const store: SplitterStore = new SplitterStore() + const state = reactive({ + panes: [], // 内嵌面板 + }) + + state.panes = ctx.slots.DSplitterPane?.() || [] + + store.setPanes({ panes: state.panes }) + + const domRef = ref() + + provide('orientation', props.orientation) + provide('splitterStore', store) + + nextTick(() => { + let containerSize = 0 + if (props.orientation === 'vertical') { + containerSize = domRef.value.clientHeight + } else { + containerSize = domRef.value.clientWidth + } + store.setSplitter({ containerSize }) + }) + + return () => { + const { splitBarSize, orientation, showCollapseButton } = props + const wrapperClass = ['devui-splitter', `devui-splitter-${orientation}`] + + return ( +
    + {state.panes} + {state.panes + .filter((pane, index, arr) => index !== arr.length - 1) + .map((pane, index) => { + return ( + + ) + })} +
    + ) + } + }, +}) diff --git a/devui/splitter/src/util/d-resize-directive.ts b/devui/splitter/src/util/d-resize-directive.ts new file mode 100644 index 00000000..69dfda5a --- /dev/null +++ b/devui/splitter/src/util/d-resize-directive.ts @@ -0,0 +1,85 @@ +import type { Directive, DirectiveBinding } from 'vue' +export interface OnResizeEvent { + (coordinateInfo: CoordinateInfo): void +} +export interface ResizeDirectiveProp { + enableResize: true // 是否允许拖动 + onPressEvent: OnResizeEvent + onDragEvent: OnResizeEvent + onReleaseEvent: OnResizeEvent +} + +export interface CoordinateInfo { + pageX: number + pageY: number + clientX: number + clientY: number + offsetX: number + offsetY: number + type: string + originalEvent: MouseEvent +} + +const resize: Directive = { + mounted(el, { value }: DirectiveBinding) { + el.$value = value + // 是否允许拖动 + if (value.enableResize) { + bindEvent(el) + } + }, + unmounted(el, { value }: DirectiveBinding) { + if (value.enableResize) { + unbind(el, 'mousedown', onMousedown) + } + }, +} + +function bindEvent(el) { + // 绑定 mousedown 事件 + bind(el, 'mousedown', onMousedown) + // TODO 绑定触屏事件 +} + +function bind(el, event, callback) { + el.addEventListener && el.addEventListener(event, callback) +} + +function unbind(el, event, callback) { + el.removeEventListener && el.removeEventListener(event, callback) +} + +function onMousedown(e) { + const $value = e?.target?.$value + if (!$value) return // 提前退出,避免 splitter-bar 子元素响应导致错误 + + bind(document, 'mousemove', onMousemove) + bind(document, 'mouseup', onMouseup) + $value.onPressEvent(normalizeEvent(e)) + + function onMousemove(e) { + $value.onDragEvent(normalizeEvent(e)) + } + + function onMouseup(e) { + unbind(document, 'mousemove', onMousemove) + unbind(document, 'mouseup', onMouseup) + $value.onReleaseEvent(normalizeEvent(e)) + } +} + +// 返回常用位置信息 +function normalizeEvent(e) { + return { + pageX: e.pageX, + pageY: e.pageY, + clientX: e.clientX, + clientY: e.clientY, + offsetX: e.offsetX, + offsetY: e.offsetY, + type: e.type, + originalEvent: e, + } +} + +export default resize diff --git a/devui/style/core/_dropdown.scss b/devui/style/core/_dropdown.scss index a4332380..085045d0 100755 --- a/devui/style/core/_dropdown.scss +++ b/devui/style/core/_dropdown.scss @@ -48,6 +48,7 @@ z-index: 1000; display: none; min-width: calc(min(100%, 102px)); + margin: 4px 0; padding-bottom: 5px; background-clip: padding-box; border-radius: 2px; diff --git a/docs/.vitepress/config/sidebar.ts b/docs/.vitepress/config/sidebar.ts new file mode 100644 index 00000000..d9d3e0cd --- /dev/null +++ b/docs/.vitepress/config/sidebar.ts @@ -0,0 +1,100 @@ +const sidebar = { + '/': [ + { text: '快速开始', link: '/' }, + { + text: '通用', + children: [ + { text: 'Button 按钮', link: '/components/button/', status: '已完成' }, + { text: 'Icon 图标', link: '/components/icon/', status: '已完成' }, + { text: 'DragDrop 拖拽', link: '/components/dragdrop/' }, + { text: 'Fullscreen 全屏', link: '/components/fullscreen/' }, + { text: 'Panel 面板', link: '/components/panel/', status: '已完成' }, + { text: 'Search 搜索框', link: '/components/search/', status: '已完成' }, + { text: 'Status 状态', link: '/components/status/', status: '已完成' }, + { text: 'Sticky 便贴', link: '/components/sticky/' }, + { text: 'Overlay 遮罩层', link: '/components/overlay/' } + ] + }, + { + text: '导航', + children: [ + { text: 'Accordion 手风琴', link: '/components/accordion/' }, + { text: 'Anchor 锚点', link: '/components/anchor/' }, + { text: 'BackTop 回到顶部', link: '/components/back-top/' }, + { text: 'Breadcrumb 面包屑', link: '/components/breadcrumb/' }, + { text: 'Dropdown 下拉菜单', link: '/components/dropdown/' }, + { text: 'NavSprite 导航精灵', link: '/components/nav-sprite/' }, + { text: 'Pagination 分页', link: '/components/pagination/', status: '开发中' }, + { text: 'StepsGuide 操作指引', link: '/components/steps-guide/' }, + { text: 'Tabs 选项卡', link: '/components/tabs/', status: '已完成' }, + { text: 'Anchor 锚点', link: '/components/Anchor/' }, + ] + }, + { + text: '反馈', + children: [ + { text: 'Alert 警告', link: '/components/alert/', status: '已完成' }, + { text: 'Drawer 抽屉板', link: '/components/drawer/' }, + { text: 'Loading 加载提示', link: '/components/loading/', status: '已完成' }, + { text: 'Mention 提及', link: '/components/mention/' }, + { text: 'Modal 模态弹窗', link: '/components/modal/' }, + { text: 'Popover 悬浮提示', link: '/components/popover/', status: "开发中" }, + { text: 'ReadTip 阅读提示', link: '/components/read-tip/' }, + { text: 'Toast 全局通知', link: '/components/toast/', status: '已完成' }, + { text: 'Tooltip 提示', link: '/components/tooltip/' }, + ] + }, + { + text: '数据录入', + children: [ + { text: 'AutoComplete 自动补全', link: '/components/auto-complete/' }, + { text: 'Cascader 级联菜单', link: '/components/cascader/' }, + { text: 'CategorySearch 分类搜索', link: '/components/category-search/' }, + { text: 'Checkbox 复选框', link: '/components/checkbox/', status: '已完成' }, + { text: 'DatePicker 日期选择器', link: '/components/date-picker/', status: '开发中' }, + { text: 'DatePickerPro 日期选择器', link: '/components/date-picker-pro/' }, + { text: 'EditableSelect 可编辑下拉框', link: '/components/editable-select/', status: '开发中' }, + { text: 'Form 表单', link: '/components/form/' }, + { text: 'Input 文本框', link: '/components/input/', status: '已完成' }, + { text: 'InputNumber 数字输入框', link: '/components/input-number/' }, + { text: 'MultiAutoComplete 多项自动补全', link: '/components/multi-auto-complete/' }, + { text: 'Radio 单选框', link: '/components/radio/', status: '已完成' }, + { text: 'Select 下拉选择框', link: '/components/select/', status: '开发中' }, + { text: 'Slider 滑块', link: '/components/slider/' }, + { text: 'Switch 开关', link: '/components/switch/', status: '已完成' }, + { text: 'TagInput 标签输入', link: '/components/tag-input/', status: '已完成' }, + { text: 'Textarea 多行文本框', link: '/components/textarea/' }, + { text: 'TimePicker 时间选择器', link: '/components/time-picker/' }, + { text: 'Transfer 穿梭框', link: '/components/transfer/' }, + { text: 'TreeSelect 树形选择框', link: '/components/tree-select/' }, + { text: 'Upload 上传', link: '/components/upload/', status: '开发中' }, + ] + }, + { + text: '数据展示', + children: [ + { text: 'Avatar 头像', link: '/components/avatar/', status: '已完成' }, + { text: 'Badge 徽标', link: '/components/badge/', status: '已完成' }, + { text: 'Card 卡片', link: '/components/card/', status: '已完成' }, + { text: 'Carousel 走马灯', link: '/components/carousel/', status: '已完成' }, + { text: 'DataTable 表格', link: '/components/data-table/' }, + { text: 'Gantt 甘特图', link: '/components/gantt/' }, + { text: 'ImagePreview 图片预览', link: '/components/image-preview/' }, + { text: 'Progress 进度条', link: '/components/progress/', status: '已完成' }, + { text: 'QuadrantDiagram 象限图', link: '/components/quadrant-diagram/' }, + { text: 'Rate 等级评估', link: '/components/rate/', status: '已完成' }, + { text: 'Tag 标签', link: '/components/tag/' }, + { text: 'Tree 树', link: '/components/tree/' }, + ] + }, + { + text: '布局', + children: [ + { text: 'Layout 布局', link: '/components/layout/' }, + { text: 'Splitter 分割器', link: '/components/splitter/', status: '开发中' } + ] + }, + ] +} + +export default sidebar diff --git a/docs/components/editable-select/index.md b/docs/components/editable-select/index.md new file mode 100644 index 00000000..f0fc0bbe --- /dev/null +++ b/docs/components/editable-select/index.md @@ -0,0 +1,34 @@ +# EditableSelect 可输入下拉选择框 + +同时支持输入和下拉选择的输入框。 + +### 何时使用 + +当需要同时支持用户输入数据和选择已有数据的时候使用,加入输入联想功能,方便用户搜索已有数据。 + +:::demo +```vue + + + +``` +::: diff --git a/docs/components/splitter/index.md b/docs/components/splitter/index.md new file mode 100644 index 00000000..802fdf98 --- /dev/null +++ b/docs/components/splitter/index.md @@ -0,0 +1,308 @@ +# Splitter 分割器 + +页面分割器。 + +**何时使用** + +需要动态调整不同页面布局区域大小的时候选择使用。 + +### 基本用法 + +:::demo + +```vue + + + + + +``` +::: + + + + +### 垂直布局用法 + +:::demo + +```vue + + + + + +``` + +::: + +### 组合布局用法 + +:::demo + +```vue + + + + + +``` + +::: + +### 指定折叠收起方向 + +:::demo + +```vue + + + + + +``` + +::: + +### 折叠收缩显示菜单【TODO】 \ No newline at end of file -- Gitee From 689f908db166e576baa0544741be1aab449e9f70 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Mon, 27 Sep 2021 10:44:47 +0800 Subject: [PATCH 5/9] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E9=A3=8E=E6=A0=BC=20md=E6=A0=BC=E5=BC=8F=20ripple=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/ripple/src/options.ts | 14 +-- devui/ripple/src/ripple-directive.ts | 8 +- devui/ripple/src/types.ts | 2 +- .../src/utils/create-container-element.ts | 21 +++++ .../ripple/src/utils/create-ripple-element.ts | 23 +++++ .../src/utils/createcontainerelement.ts | 21 ----- devui/ripple/src/utils/createwaveelement.ts | 23 ----- ...ner.ts => getdistance-tofurthestcorner.ts} | 2 +- ...ativepointer.ts => getrelative-pointer.ts} | 0 devui/ripple/src/utils/hookkeys.ts | 16 ---- devui/ripple/src/utils/isvue3.ts | 10 --- devui/ripple/src/utils/ripple-count.ts | 23 +++++ devui/ripple/src/utils/wave-count.ts | 23 ----- devui/ripple/src/{v-wave.ts => v-ripple.ts} | 88 +++++++++---------- docs/components/ripple/index.md | 10 +-- 15 files changed, 129 insertions(+), 155 deletions(-) create mode 100644 devui/ripple/src/utils/create-container-element.ts create mode 100644 devui/ripple/src/utils/create-ripple-element.ts delete mode 100644 devui/ripple/src/utils/createcontainerelement.ts delete mode 100644 devui/ripple/src/utils/createwaveelement.ts rename devui/ripple/src/utils/{getdistancetofurthestcorner.ts => getdistance-tofurthestcorner.ts} (90%) rename devui/ripple/src/utils/{getrelativepointer.ts => getrelative-pointer.ts} (100%) delete mode 100644 devui/ripple/src/utils/hookkeys.ts delete mode 100644 devui/ripple/src/utils/isvue3.ts create mode 100644 devui/ripple/src/utils/ripple-count.ts delete mode 100644 devui/ripple/src/utils/wave-count.ts rename devui/ripple/src/{v-wave.ts => v-ripple.ts} (31%) diff --git a/devui/ripple/src/options.ts b/devui/ripple/src/options.ts index 8288d4fe..d7fc2142 100644 --- a/devui/ripple/src/options.ts +++ b/devui/ripple/src/options.ts @@ -1,4 +1,4 @@ -interface IVWaveDirectiveOptions { +interface IVrippleDirectiveOptions { /** * * @remarks @@ -47,7 +47,7 @@ interface IVWaveDirectiveOptions { cancellationPeriod: number } -interface IVWavePluginOptions extends IVWaveDirectiveOptions { +interface IVripplePluginOptions extends IVrippleDirectiveOptions { /** * 用于覆盖指令的名称 * @@ -56,7 +56,7 @@ interface IVWavePluginOptions extends IVWaveDirectiveOptions { * @example * * @default - * 默认指令 wave + * 默认指令 ripple */ directive: string } @@ -64,11 +64,11 @@ interface IVWavePluginOptions extends IVWaveDirectiveOptions { // 给可预见值 value 添加类型 interface IRippleDirectiveOptionWithBinding { - value: IVWaveDirectiveOptions + value: IVrippleDirectiveOptions } -const DEFAULT_PLUGIN_OPTIONS: IVWavePluginOptions = { - directive: 'wave', +const DEFAULT_PLUGIN_OPTIONS: IVripplePluginOptions = { + directive: 'ripple', color: 'currentColor', initialOpacity: 0.2, finalOpacity: 0.1, @@ -77,4 +77,4 @@ const DEFAULT_PLUGIN_OPTIONS: IVWavePluginOptions = { cancellationPeriod: 75 } -export { DEFAULT_PLUGIN_OPTIONS, IVWavePluginOptions, IVWaveDirectiveOptions, IRippleDirectiveOptionWithBinding } +export { DEFAULT_PLUGIN_OPTIONS, IVripplePluginOptions, IVrippleDirectiveOptions, IRippleDirectiveOptionWithBinding } diff --git a/devui/ripple/src/ripple-directive.ts b/devui/ripple/src/ripple-directive.ts index 7612cd91..66c300d5 100644 --- a/devui/ripple/src/ripple-directive.ts +++ b/devui/ripple/src/ripple-directive.ts @@ -1,13 +1,13 @@ // can export function. 解构参数类型冗余 新定义insterface IRippleDirectiveOptionWithBinding import { DEFAULT_PLUGIN_OPTIONS, - IVWaveDirectiveOptions, + IVrippleDirectiveOptions, IRippleDirectiveOptionWithBinding } from './options' -import { wave } from './v-wave' +import { ripple } from './v-ripple' const optionMap = new WeakMap< HTMLElement, - Partial | false + Partial | false >() const globalOptions = { ...DEFAULT_PLUGIN_OPTIONS } export default { @@ -19,7 +19,7 @@ export default { if (options === false) return - wave(event, el, { + ripple(event, el, { ...globalOptions, ...options }) diff --git a/devui/ripple/src/types.ts b/devui/ripple/src/types.ts index 782c333d..c3c69b15 100644 --- a/devui/ripple/src/types.ts +++ b/devui/ripple/src/types.ts @@ -50,7 +50,7 @@ interface IRipplePluginOption extends IRippleDirectiveOption { * @example * * @default - * '默认指令wave + * '默认指令ripple */ directive: string } diff --git a/devui/ripple/src/utils/create-container-element.ts b/devui/ripple/src/utils/create-container-element.ts new file mode 100644 index 00000000..36cfee3e --- /dev/null +++ b/devui/ripple/src/utils/create-container-element.ts @@ -0,0 +1,21 @@ +export const createContainer = ({ + borderTopLeftRadius, + borderTopRightRadius, + borderBottomLeftRadius, + borderBottomRightRadius +}: CSSStyleDeclaration): HTMLElement => { + const rippleContainer = document.createElement('div') + rippleContainer.style.top = '0' + rippleContainer.style.left = '0' + rippleContainer.style.width = '100%' + rippleContainer.style.height = '100%' + rippleContainer.style.position = 'absolute' + rippleContainer.style.borderRadius = `${borderTopLeftRadius} ${borderTopRightRadius} ${borderBottomRightRadius} ${borderBottomLeftRadius}` + rippleContainer.style.overflow = 'hidden' + rippleContainer.style.pointerEvents = 'none' + + // 兼容 ie 苹果 + rippleContainer.style.webkitMaskImage = '-webkit-radial-gradient(white, black)' + + return rippleContainer +} diff --git a/devui/ripple/src/utils/create-ripple-element.ts b/devui/ripple/src/utils/create-ripple-element.ts new file mode 100644 index 00000000..376d055e --- /dev/null +++ b/devui/ripple/src/utils/create-ripple-element.ts @@ -0,0 +1,23 @@ +import { IVrippleDirectiveOptions } from '../options' + +export const createrippleElement = ( + x: number, + y: number, + size: number, + options: IVrippleDirectiveOptions +): HTMLElement => { + const rippleElement = document.createElement('div') + + rippleElement.style.position = 'absolute' + rippleElement.style.width = `${size}px` + rippleElement.style.height = `${size}px` + rippleElement.style.top = `${y}px` + rippleElement.style.left = `${x}px` + rippleElement.style.background = options.color + rippleElement.style.borderRadius = '50%' + rippleElement.style.opacity = `${options.initialOpacity}` + rippleElement.style.transform = `translate(-50%,-50%) scale(0)` + rippleElement.style.transition = `transform ${options.duration}s ${options.easing}, opacity ${options.duration}s ${options.easing}` + + return rippleElement +} diff --git a/devui/ripple/src/utils/createcontainerelement.ts b/devui/ripple/src/utils/createcontainerelement.ts deleted file mode 100644 index 39b09bff..00000000 --- a/devui/ripple/src/utils/createcontainerelement.ts +++ /dev/null @@ -1,21 +0,0 @@ -export const createContainer = ({ - borderTopLeftRadius, - borderTopRightRadius, - borderBottomLeftRadius, - borderBottomRightRadius -}: CSSStyleDeclaration): HTMLElement => { - const waveContainer = document.createElement('div') - waveContainer.style.top = '0' - waveContainer.style.left = '0' - waveContainer.style.width = '100%' - waveContainer.style.height = '100%' - waveContainer.style.position = 'absolute' - waveContainer.style.borderRadius = `${borderTopLeftRadius} ${borderTopRightRadius} ${borderBottomRightRadius} ${borderBottomLeftRadius}` - waveContainer.style.overflow = 'hidden' - waveContainer.style.pointerEvents = 'none' - - // 兼容 ie 苹果 - waveContainer.style.webkitMaskImage = '-webkit-radial-gradient(white, black)' - - return waveContainer -} diff --git a/devui/ripple/src/utils/createwaveelement.ts b/devui/ripple/src/utils/createwaveelement.ts deleted file mode 100644 index 824b04b0..00000000 --- a/devui/ripple/src/utils/createwaveelement.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { IVWaveDirectiveOptions } from '../options' - -export const createWaveElement = ( - x: number, - y: number, - size: number, - options: IVWaveDirectiveOptions -): HTMLElement => { - const waveElement = document.createElement('div') - - waveElement.style.position = 'absolute' - waveElement.style.width = `${size}px` - waveElement.style.height = `${size}px` - waveElement.style.top = `${y}px` - waveElement.style.left = `${x}px` - waveElement.style.background = options.color - waveElement.style.borderRadius = '50%' - waveElement.style.opacity = `${options.initialOpacity}` - waveElement.style.transform = `translate(-50%,-50%) scale(0)` - waveElement.style.transition = `transform ${options.duration}s ${options.easing}, opacity ${options.duration}s ${options.easing}` - - return waveElement -} diff --git a/devui/ripple/src/utils/getdistancetofurthestcorner.ts b/devui/ripple/src/utils/getdistance-tofurthestcorner.ts similarity index 90% rename from devui/ripple/src/utils/getdistancetofurthestcorner.ts rename to devui/ripple/src/utils/getdistance-tofurthestcorner.ts index 0b66d97f..abed1695 100644 --- a/devui/ripple/src/utils/getdistancetofurthestcorner.ts +++ b/devui/ripple/src/utils/getdistance-tofurthestcorner.ts @@ -5,10 +5,10 @@ export function getDistanceToFurthestCorner( y: number, { width, height }: DOMRect ): number { + // 获取点击到每个边界的距离 const topLeft = magnitude(x, y, 0, 0) const topRight = magnitude(x, y, width, 0) const bottomLeft = magnitude(x, y, 0, height) const bottomRight = magnitude(x, y, width, height) - return Math.max(topLeft, topRight, bottomLeft, bottomRight) } diff --git a/devui/ripple/src/utils/getrelativepointer.ts b/devui/ripple/src/utils/getrelative-pointer.ts similarity index 100% rename from devui/ripple/src/utils/getrelativepointer.ts rename to devui/ripple/src/utils/getrelative-pointer.ts diff --git a/devui/ripple/src/utils/hookkeys.ts b/devui/ripple/src/utils/hookkeys.ts deleted file mode 100644 index 87a92a0f..00000000 --- a/devui/ripple/src/utils/hookkeys.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { App } from 'vue' -import { isVue3 } from './isVue3' - -const getHooks = (app: App) => { - return isVue3(app) - ? { - mounted: 'mounted', - updated: 'updated' - } - : { - mounted: 'inserted', - updated: 'componentUpdated' - } -} - -export { getHooks } diff --git a/devui/ripple/src/utils/isvue3.ts b/devui/ripple/src/utils/isvue3.ts deleted file mode 100644 index 7e1597ed..00000000 --- a/devui/ripple/src/utils/isvue3.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { App } from 'vue' - -interface Vue2 { - default: { - version: string - } -} - -const isVue3 = (app: Vue2 | App): app is App => 'config' in app && 'globalProperties' in app.config -export { isVue3 } diff --git a/devui/ripple/src/utils/ripple-count.ts b/devui/ripple/src/utils/ripple-count.ts new file mode 100644 index 00000000..f3a8911b --- /dev/null +++ b/devui/ripple/src/utils/ripple-count.ts @@ -0,0 +1,23 @@ +const ripple_COUNT = 'vrippleCountInternal' + +export function incrementrippleCount(el: HTMLElement) { + const count = getrippleCount(el) + setrippleCount(el, count + 1) +} + +export function decrementrippleCount(el: HTMLElement) { + const count = getrippleCount(el) + setrippleCount(el, count - 1) +} + +function setrippleCount(el: HTMLElement, count: number) { + el.dataset[ripple_COUNT] = count.toString() +} + +export function getrippleCount(el: HTMLElement): number { + return parseInt(el.dataset[ripple_COUNT] ?? '0', 10) +} + +export function deleterippleCount(el: HTMLElement) { + delete el.dataset[ripple_COUNT] +} diff --git a/devui/ripple/src/utils/wave-count.ts b/devui/ripple/src/utils/wave-count.ts deleted file mode 100644 index 264d8d4f..00000000 --- a/devui/ripple/src/utils/wave-count.ts +++ /dev/null @@ -1,23 +0,0 @@ -const WAVE_COUNT = 'vWaveCountInternal' - -export function incrementWaveCount(el: HTMLElement) { - const count = getWaveCount(el) - setWaveCount(el, count + 1) -} - -export function decrementWaveCount(el: HTMLElement) { - const count = getWaveCount(el) - setWaveCount(el, count - 1) -} - -function setWaveCount(el: HTMLElement, count: number) { - el.dataset[WAVE_COUNT] = count.toString() -} - -export function getWaveCount(el: HTMLElement): number { - return parseInt(el.dataset[WAVE_COUNT] ?? '0', 10) -} - -export function deleteWaveCount(el: HTMLElement) { - delete el.dataset[WAVE_COUNT] -} diff --git a/devui/ripple/src/v-wave.ts b/devui/ripple/src/v-ripple.ts similarity index 31% rename from devui/ripple/src/v-wave.ts rename to devui/ripple/src/v-ripple.ts index 88188e8a..ae6d29bd 100644 --- a/devui/ripple/src/v-wave.ts +++ b/devui/ripple/src/v-ripple.ts @@ -1,19 +1,19 @@ -import { createContainer } from './utils/createcontainerelement' -import { createWaveElement } from './utils/createwaveelement' -import { getDistanceToFurthestCorner } from './utils/getdistancetofurthestcorner' -import { getRelativePointer } from './utils/getRelativePointer' +import { createContainer } from './utils/create-container-element' +import { createrippleElement } from './utils/create-ripple-element' +import { getDistanceToFurthestCorner } from './utils/getdistance-tofurthestcorner' +import { getRelativePointer } from './utils/getrelative-pointer' import { - decrementWaveCount, - deleteWaveCount, - getWaveCount, - incrementWaveCount -} from './utils/wave-count' -import { IVWaveDirectiveOptions } from './options' - -const wave = ( + decrementrippleCount, + deleterippleCount, + getrippleCount, + incrementrippleCount +} from './utils/ripple-count' +import { IVrippleDirectiveOptions } from './options' + +const ripple = ( event: PointerEvent, el: HTMLElement, - options: IVWaveDirectiveOptions + options: IVrippleDirectiveOptions ) => { const rect = el.getBoundingClientRect() const computedStyles = window.getComputedStyle(el) @@ -21,10 +21,10 @@ const wave = ( const { x, y } = getRelativePointer(event, rect) const size = 2.05 * getDistanceToFurthestCorner(x, y, rect) // 2.05 is magic, deal with it. - const waveContainer = createContainer(computedStyles) - const waveEl = createWaveElement(x, y, size, options) + const rippleContainer = createContainer(computedStyles) + const rippleEl = createrippleElement(x, y, size, options) - incrementWaveCount(el) + incrementrippleCount(el) let originalPositionValue = '' if (computedStyles.position === 'static') { @@ -32,60 +32,60 @@ const wave = ( el.style.position = 'relative' } - waveContainer.appendChild(waveEl) - el.appendChild(waveContainer) + rippleContainer.appendChild(rippleEl) + el.appendChild(rippleContainer) - let shouldDissolveWave = false - const releaseWave = (e?: any) => { + let shouldDissolveripple = false + const releaseripple = (e?: any) => { if (typeof e !== 'undefined') { - document.removeEventListener('pointerup', releaseWave) - document.removeEventListener('pointercancel', releaseWave) + document.removeEventListener('pointerup', releaseripple) + document.removeEventListener('pointercancel', releaseripple) } - if (shouldDissolveWave) dissolveWave() - else shouldDissolveWave = true + if (shouldDissolveripple) dissolveripple() + else shouldDissolveripple = true } - const dissolveWave = () => { - waveEl.style.transition = 'opacity 150ms linear' - waveEl.style.opacity = '0' + const dissolveripple = () => { + rippleEl.style.transition = 'opacity 150ms linear' + rippleEl.style.opacity = '0' setTimeout(() => { - waveContainer.remove() + rippleContainer.remove() - decrementWaveCount(el) + decrementrippleCount(el) - if (getWaveCount(el) === 0) { - deleteWaveCount(el) + if (getrippleCount(el) === 0) { + deleterippleCount(el) el.style.position = originalPositionValue } }, 150) } - document.addEventListener('pointerup', releaseWave) - document.addEventListener('pointercancel', releaseWave) + document.addEventListener('pointerup', releaseripple) + document.addEventListener('pointercancel', releaseripple) const token = setTimeout(() => { - document.removeEventListener('pointercancel', cancelWave) + document.removeEventListener('pointercancel', cancelripple) requestAnimationFrame(() => { - waveEl.style.transform = `translate(-50%,-50%) scale(1)` - waveEl.style.opacity = `${options.finalOpacity}` + rippleEl.style.transform = `translate(-50%,-50%) scale(1)` + rippleEl.style.opacity = `${options.finalOpacity}` - setTimeout(() => releaseWave(), options.duration * 1000) + setTimeout(() => releaseripple(), options.duration * 1000) }) }, options.cancellationPeriod) - const cancelWave = () => { + const cancelripple = () => { clearTimeout(token) - waveContainer.remove() - document.removeEventListener('pointerup', releaseWave) - document.removeEventListener('pointercancel', releaseWave) - document.removeEventListener('pointercancel', cancelWave) + rippleContainer.remove() + document.removeEventListener('pointerup', releaseripple) + document.removeEventListener('pointercancel', releaseripple) + document.removeEventListener('pointercancel', cancelripple) } - document.addEventListener('pointercancel', cancelWave) + document.addEventListener('pointercancel', cancelripple) } -export { wave } +export { ripple } diff --git a/docs/components/ripple/index.md b/docs/components/ripple/index.md index c8844f6c..373b0886 100644 --- a/docs/components/ripple/index.md +++ b/docs/components/ripple/index.md @@ -145,11 +145,11 @@ export default defineComponent({ | 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 | | :---------: | :------: | :-------: | :----------------------- | --------------------------------- | --------- | -| color | `string` | #00050 | 可选,默认当前文本颜色 | || -| initialOpacity | `number` | 0.1 | 可选,初始交互效果透明度大小 | || -| finalOpacity | `number` | 0.1 | 可选,结束交互效果长按透明度大小 | || +| color | `string` | #00050 | 可选,默认当前文本颜色 | | +| initialOpacity | `number` | 0.1 | 可选,初始交互效果透明度大小 | | +| finalOpacity | `number` | 0.1 | 可选,结束交互效果长按透明度大小 | | | duration | `number` | 0.4s | 可选,持续时间 | || -| easing | `string` | ease-out | 可选,缓动动画 | || -| cancellationPeriod | `number` | 75ms | 可选,延迟debouceTime时间后调用 | || +| easing | `string` | ease-out | 可选,缓动动画 | | +| cancellationPeriod | `number` | 75ms | 可选,延迟debouceTime时间后调用 | -- Gitee From 17caf01702d8058aeba7ff16702cf182044eec06 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Mon, 27 Sep 2021 10:47:25 +0800 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20md=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/ripple/src/utils/getdistance-tofurthestcorner.ts | 2 +- docs/components/ripple/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/devui/ripple/src/utils/getdistance-tofurthestcorner.ts b/devui/ripple/src/utils/getdistance-tofurthestcorner.ts index abed1695..cadb428d 100644 --- a/devui/ripple/src/utils/getdistance-tofurthestcorner.ts +++ b/devui/ripple/src/utils/getdistance-tofurthestcorner.ts @@ -5,7 +5,7 @@ export function getDistanceToFurthestCorner( y: number, { width, height }: DOMRect ): number { - // 获取点击到每个边界的距离 + // 获取点击目标的位置到块级作用域边界的距离 const topLeft = magnitude(x, y, 0, 0) const topRight = magnitude(x, y, width, 0) const bottomLeft = magnitude(x, y, 0, height) diff --git a/docs/components/ripple/index.md b/docs/components/ripple/index.md index 373b0886..2e2b32fe 100644 --- a/docs/components/ripple/index.md +++ b/docs/components/ripple/index.md @@ -148,7 +148,7 @@ export default defineComponent({ | color | `string` | #00050 | 可选,默认当前文本颜色 | | | initialOpacity | `number` | 0.1 | 可选,初始交互效果透明度大小 | | | finalOpacity | `number` | 0.1 | 可选,结束交互效果长按透明度大小 | | -| duration | `number` | 0.4s | 可选,持续时间 | || +| duration | `number` | 0.4s | 可选,持续时间 | | | easing | `string` | ease-out | 可选,缓动动画 | | | cancellationPeriod | `number` | 75ms | 可选,延迟debouceTime时间后调用 | -- Gitee From 7a7e932408c3fd4be7bbc85be76976ed65c5aa74 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Mon, 27 Sep 2021 10:51:26 +0800 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20ripple=E7=B1=BB=E5=9E=8B=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/ripple/src/options.ts | 10 +++++----- devui/ripple/src/ripple-directive.ts | 4 ++-- devui/ripple/src/utils/create-ripple-element.ts | 4 ++-- devui/ripple/src/v-ripple.ts | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/devui/ripple/src/options.ts b/devui/ripple/src/options.ts index d7fc2142..cb948f3b 100644 --- a/devui/ripple/src/options.ts +++ b/devui/ripple/src/options.ts @@ -1,4 +1,4 @@ -interface IVrippleDirectiveOptions { +interface IVRippleDirectiveOptions { /** * * @remarks @@ -47,7 +47,7 @@ interface IVrippleDirectiveOptions { cancellationPeriod: number } -interface IVripplePluginOptions extends IVrippleDirectiveOptions { +interface IVRipplePluginOptions extends IVRippleDirectiveOptions { /** * 用于覆盖指令的名称 * @@ -64,10 +64,10 @@ interface IVripplePluginOptions extends IVrippleDirectiveOptions { // 给可预见值 value 添加类型 interface IRippleDirectiveOptionWithBinding { - value: IVrippleDirectiveOptions + value: IVRippleDirectiveOptions } -const DEFAULT_PLUGIN_OPTIONS: IVripplePluginOptions = { +const DEFAULT_PLUGIN_OPTIONS: IVRipplePluginOptions = { directive: 'ripple', color: 'currentColor', initialOpacity: 0.2, @@ -77,4 +77,4 @@ const DEFAULT_PLUGIN_OPTIONS: IVripplePluginOptions = { cancellationPeriod: 75 } -export { DEFAULT_PLUGIN_OPTIONS, IVripplePluginOptions, IVrippleDirectiveOptions, IRippleDirectiveOptionWithBinding } +export { DEFAULT_PLUGIN_OPTIONS, IVRipplePluginOptions, IVRippleDirectiveOptions, IRippleDirectiveOptionWithBinding } diff --git a/devui/ripple/src/ripple-directive.ts b/devui/ripple/src/ripple-directive.ts index 66c300d5..06a0fd0f 100644 --- a/devui/ripple/src/ripple-directive.ts +++ b/devui/ripple/src/ripple-directive.ts @@ -1,13 +1,13 @@ // can export function. 解构参数类型冗余 新定义insterface IRippleDirectiveOptionWithBinding import { DEFAULT_PLUGIN_OPTIONS, - IVrippleDirectiveOptions, + IVRippleDirectiveOptions, IRippleDirectiveOptionWithBinding } from './options' import { ripple } from './v-ripple' const optionMap = new WeakMap< HTMLElement, - Partial | false + Partial | false >() const globalOptions = { ...DEFAULT_PLUGIN_OPTIONS } export default { diff --git a/devui/ripple/src/utils/create-ripple-element.ts b/devui/ripple/src/utils/create-ripple-element.ts index 376d055e..6358e765 100644 --- a/devui/ripple/src/utils/create-ripple-element.ts +++ b/devui/ripple/src/utils/create-ripple-element.ts @@ -1,10 +1,10 @@ -import { IVrippleDirectiveOptions } from '../options' +import { IVRippleDirectiveOptions } from '../options' export const createrippleElement = ( x: number, y: number, size: number, - options: IVrippleDirectiveOptions + options: IVRippleDirectiveOptions ): HTMLElement => { const rippleElement = document.createElement('div') diff --git a/devui/ripple/src/v-ripple.ts b/devui/ripple/src/v-ripple.ts index ae6d29bd..f08248c1 100644 --- a/devui/ripple/src/v-ripple.ts +++ b/devui/ripple/src/v-ripple.ts @@ -8,12 +8,12 @@ import { getrippleCount, incrementrippleCount } from './utils/ripple-count' -import { IVrippleDirectiveOptions } from './options' +import { IVRippleDirectiveOptions } from './options' const ripple = ( event: PointerEvent, el: HTMLElement, - options: IVrippleDirectiveOptions + options: IVRippleDirectiveOptions ) => { const rect = el.getBoundingClientRect() const computedStyles = window.getComputedStyle(el) -- Gitee From 47ae4448db63554659771545ea67f1a04f6ae894 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Mon, 27 Sep 2021 16:15:40 +0800 Subject: [PATCH 8/9] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E9=A9=BC=E5=B3=B0?= =?UTF-8?q?=E5=91=BD=E5=90=8D=20=E6=95=B0=E5=AD=97=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=B8=B8=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/ripple/src/options.ts | 4 ++-- devui/ripple/src/types.ts | 4 ++-- devui/ripple/src/utils/ripple-count.ts | 26 +++++++++++++------------- devui/ripple/src/v-ripple.ts | 23 +++++++++++------------ docs/components/ripple/index.md | 6 +++--- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/devui/ripple/src/options.ts b/devui/ripple/src/options.ts index cb948f3b..5ce4df68 100644 --- a/devui/ripple/src/options.ts +++ b/devui/ripple/src/options.ts @@ -44,7 +44,7 @@ interface IVRippleDirectiveOptions { * @default * 75 */ - cancellationPeriod: number + delayTime: number } interface IVRipplePluginOptions extends IVRippleDirectiveOptions { @@ -74,7 +74,7 @@ const DEFAULT_PLUGIN_OPTIONS: IVRipplePluginOptions = { finalOpacity: 0.1, duration: 0.8, easing: 'ease-out', - cancellationPeriod: 75 + delayTime: 75 } export { DEFAULT_PLUGIN_OPTIONS, IVRipplePluginOptions, IVRippleDirectiveOptions, IRippleDirectiveOptionWithBinding } diff --git a/devui/ripple/src/types.ts b/devui/ripple/src/types.ts index c3c69b15..6480162f 100644 --- a/devui/ripple/src/types.ts +++ b/devui/ripple/src/types.ts @@ -40,7 +40,7 @@ interface IRippleDirectiveOption { /** * 取消延迟时间 */ - cancellationPeriod: number + delayTime: number } interface IRipplePluginOption extends IRippleDirectiveOption { /** @@ -62,7 +62,7 @@ const DEFAULT_PLUGIN_OPTIONS: IRipplePluginOption = { finalOpacity: 0.1, duration: 0.4, easing: 'ease-out', - cancellationPeriod: 75 + delayTime: 75 } export { DEFAULT_PLUGIN_OPTIONS, IRipplePluginOption, IRippleDirectiveOption } diff --git a/devui/ripple/src/utils/ripple-count.ts b/devui/ripple/src/utils/ripple-count.ts index f3a8911b..6d9720d1 100644 --- a/devui/ripple/src/utils/ripple-count.ts +++ b/devui/ripple/src/utils/ripple-count.ts @@ -1,23 +1,23 @@ -const ripple_COUNT = 'vrippleCountInternal' +const RIPPLE_COUNT = 'vRippleCountInternal' -export function incrementrippleCount(el: HTMLElement) { - const count = getrippleCount(el) - setrippleCount(el, count + 1) +export function incrementRippleCount(el: HTMLElement) { + const count = getRippleCount(el) + setRippleCount(el, count + 1) } -export function decrementrippleCount(el: HTMLElement) { - const count = getrippleCount(el) - setrippleCount(el, count - 1) +export function decrementRippleCount(el: HTMLElement) { + const count = getRippleCount(el) + setRippleCount(el, count - 1) } -function setrippleCount(el: HTMLElement, count: number) { - el.dataset[ripple_COUNT] = count.toString() +function setRippleCount(el: HTMLElement, count: number) { + el.dataset[RIPPLE_COUNT] = count.toString() } -export function getrippleCount(el: HTMLElement): number { - return parseInt(el.dataset[ripple_COUNT] ?? '0', 10) +export function getRippleCount(el: HTMLElement): number { + return parseInt(el.dataset[RIPPLE_COUNT] ?? '0', 10) } -export function deleterippleCount(el: HTMLElement) { - delete el.dataset[ripple_COUNT] +export function deleteRippleCount(el: HTMLElement) { + delete el.dataset[RIPPLE_COUNT] } diff --git a/devui/ripple/src/v-ripple.ts b/devui/ripple/src/v-ripple.ts index f08248c1..a0b42d36 100644 --- a/devui/ripple/src/v-ripple.ts +++ b/devui/ripple/src/v-ripple.ts @@ -3,13 +3,13 @@ import { createrippleElement } from './utils/create-ripple-element' import { getDistanceToFurthestCorner } from './utils/getdistance-tofurthestcorner' import { getRelativePointer } from './utils/getrelative-pointer' import { - decrementrippleCount, - deleterippleCount, - getrippleCount, - incrementrippleCount + decrementRippleCount, + deleteRippleCount, + getRippleCount, + incrementRippleCount } from './utils/ripple-count' import { IVRippleDirectiveOptions } from './options' - +const MULTIPLE_NUMBER = 2.05 const ripple = ( event: PointerEvent, el: HTMLElement, @@ -17,14 +17,13 @@ const ripple = ( ) => { const rect = el.getBoundingClientRect() const computedStyles = window.getComputedStyle(el) - const { x, y } = getRelativePointer(event, rect) - const size = 2.05 * getDistanceToFurthestCorner(x, y, rect) // 2.05 is magic, deal with it. + const size = MULTIPLE_NUMBER * getDistanceToFurthestCorner(x, y, rect) const rippleContainer = createContainer(computedStyles) const rippleEl = createrippleElement(x, y, size, options) - incrementrippleCount(el) + incrementRippleCount(el) let originalPositionValue = '' if (computedStyles.position === 'static') { @@ -53,10 +52,10 @@ const ripple = ( setTimeout(() => { rippleContainer.remove() - decrementrippleCount(el) + decrementRippleCount(el) - if (getrippleCount(el) === 0) { - deleterippleCount(el) + if (getRippleCount(el) === 0) { + deleteRippleCount(el) el.style.position = originalPositionValue } }, 150) @@ -74,7 +73,7 @@ const ripple = ( setTimeout(() => releaseripple(), options.duration * 1000) }) - }, options.cancellationPeriod) + }, options.delayTime) const cancelripple = () => { clearTimeout(token) diff --git a/docs/components/ripple/index.md b/docs/components/ripple/index.md index 2e2b32fe..0de25398 100644 --- a/docs/components/ripple/index.md +++ b/docs/components/ripple/index.md @@ -146,10 +146,10 @@ export default defineComponent({ | 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 | | :---------: | :------: | :-------: | :----------------------- | --------------------------------- | --------- | | color | `string` | #00050 | 可选,默认当前文本颜色 | | -| initialOpacity | `number` | 0.1 | 可选,初始交互效果透明度大小 | | -| finalOpacity | `number` | 0.1 | 可选,结束交互效果长按透明度大小 | | +| initial-opacity | `number` | 0.1 | 可选,初始交互效果透明度大小 | | +| final-opacity | `number` | 0.1 | 可选,结束交互效果长按透明度大小 | | | duration | `number` | 0.4s | 可选,持续时间 | | | easing | `string` | ease-out | 可选,缓动动画 | | -| cancellationPeriod | `number` | 75ms | 可选,延迟debouceTime时间后调用 | +| delay-time | `number` | 75ms | 可选,延迟debouceTime时间后调用 | -- Gitee From ae1401e10cd9bc9569646451c22341ab215003a9 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Mon, 27 Sep 2021 16:32:30 +0800 Subject: [PATCH 9/9] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9IVRipple=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/ripple/src/options.ts | 10 +-- devui/ripple/src/ripple-directive.ts | 4 +- devui/ripple/src/types.ts | 68 ------------------- .../ripple/src/utils/create-ripple-element.ts | 4 +- devui/ripple/src/v-ripple.ts | 4 +- 5 files changed, 11 insertions(+), 79 deletions(-) delete mode 100644 devui/ripple/src/types.ts diff --git a/devui/ripple/src/options.ts b/devui/ripple/src/options.ts index 5ce4df68..7a5f9168 100644 --- a/devui/ripple/src/options.ts +++ b/devui/ripple/src/options.ts @@ -1,4 +1,4 @@ -interface IVRippleDirectiveOptions { +interface IRippleDirectiveOptions { /** * * @remarks @@ -47,7 +47,7 @@ interface IVRippleDirectiveOptions { delayTime: number } -interface IVRipplePluginOptions extends IVRippleDirectiveOptions { +interface IRipplePluginOptions extends IRippleDirectiveOptions { /** * 用于覆盖指令的名称 * @@ -64,10 +64,10 @@ interface IVRipplePluginOptions extends IVRippleDirectiveOptions { // 给可预见值 value 添加类型 interface IRippleDirectiveOptionWithBinding { - value: IVRippleDirectiveOptions + value: IRippleDirectiveOptions } -const DEFAULT_PLUGIN_OPTIONS: IVRipplePluginOptions = { +const DEFAULT_PLUGIN_OPTIONS: IRipplePluginOptions = { directive: 'ripple', color: 'currentColor', initialOpacity: 0.2, @@ -77,4 +77,4 @@ const DEFAULT_PLUGIN_OPTIONS: IVRipplePluginOptions = { delayTime: 75 } -export { DEFAULT_PLUGIN_OPTIONS, IVRipplePluginOptions, IVRippleDirectiveOptions, IRippleDirectiveOptionWithBinding } +export { DEFAULT_PLUGIN_OPTIONS, IRipplePluginOptions, IRippleDirectiveOptions, IRippleDirectiveOptionWithBinding } diff --git a/devui/ripple/src/ripple-directive.ts b/devui/ripple/src/ripple-directive.ts index 06a0fd0f..d7b4730c 100644 --- a/devui/ripple/src/ripple-directive.ts +++ b/devui/ripple/src/ripple-directive.ts @@ -1,13 +1,13 @@ // can export function. 解构参数类型冗余 新定义insterface IRippleDirectiveOptionWithBinding import { DEFAULT_PLUGIN_OPTIONS, - IVRippleDirectiveOptions, + IRippleDirectiveOptions, IRippleDirectiveOptionWithBinding } from './options' import { ripple } from './v-ripple' const optionMap = new WeakMap< HTMLElement, - Partial | false + Partial | false >() const globalOptions = { ...DEFAULT_PLUGIN_OPTIONS } export default { diff --git a/devui/ripple/src/types.ts b/devui/ripple/src/types.ts deleted file mode 100644 index 6480162f..00000000 --- a/devui/ripple/src/types.ts +++ /dev/null @@ -1,68 +0,0 @@ -// 插件自动安装 兼容 vue2 vue3 默认通过 hook 安装 本次 插件 详见 ripple-directives.ts - -interface IRippleDirectiveOption { - // @remarks - /** - * 你可以设置 ·currentColor· to 能够自动使用元素的文本颜色 - * default currentColor : string - */ - // @default - color: string - /** - * the first appear in this ripple 第一次出现的透明度 - * - * @default - * 0.2 默认opacity 0.2 - */ - initialOpacity: number - /** - * 在透明度 结束的时候 stopped 的时候 我们设置透明度的大小 - * - * @default - * 0.1 - */ - finalOpacity: number - /** - * 动画持续事件 - * - * @default - * 0.4 - * - */ - duration: number - /** - * css 动画 从开始到结束 以相同的时间来执行动画 - * - * @default - * ‘ease-out’ - */ - easing: string - /** - * 取消延迟时间 - */ - delayTime: number -} -interface IRipplePluginOption extends IRippleDirectiveOption { - /** - * - * @remarks - * - * @example - * - * @default - * '默认指令ripple - */ - directive: string -} - -const DEFAULT_PLUGIN_OPTIONS: IRipplePluginOption = { - directive: 'ripple', - color: 'currentColor', - initialOpacity: 0.2, - finalOpacity: 0.1, - duration: 0.4, - easing: 'ease-out', - delayTime: 75 -} - -export { DEFAULT_PLUGIN_OPTIONS, IRipplePluginOption, IRippleDirectiveOption } diff --git a/devui/ripple/src/utils/create-ripple-element.ts b/devui/ripple/src/utils/create-ripple-element.ts index 6358e765..87a69faf 100644 --- a/devui/ripple/src/utils/create-ripple-element.ts +++ b/devui/ripple/src/utils/create-ripple-element.ts @@ -1,10 +1,10 @@ -import { IVRippleDirectiveOptions } from '../options' +import { IRippleDirectiveOptions } from '../options' export const createrippleElement = ( x: number, y: number, size: number, - options: IVRippleDirectiveOptions + options: IRippleDirectiveOptions ): HTMLElement => { const rippleElement = document.createElement('div') diff --git a/devui/ripple/src/v-ripple.ts b/devui/ripple/src/v-ripple.ts index a0b42d36..7d7586cc 100644 --- a/devui/ripple/src/v-ripple.ts +++ b/devui/ripple/src/v-ripple.ts @@ -8,12 +8,12 @@ import { getRippleCount, incrementRippleCount } from './utils/ripple-count' -import { IVRippleDirectiveOptions } from './options' +import { IRippleDirectiveOptions } from './options' const MULTIPLE_NUMBER = 2.05 const ripple = ( event: PointerEvent, el: HTMLElement, - options: IVRippleDirectiveOptions + options: IRippleDirectiveOptions ) => { const rect = el.getBoundingClientRect() const computedStyles = window.getComputedStyle(el) -- Gitee