-
Notifications
You must be signed in to change notification settings - Fork 140
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cdk: overlay): add cdk overlay (#176)
- Loading branch information
1 parent
37b8a68
commit 9e3628e
Showing
17 changed files
with
948 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
import type { OverlayOptions, OverlayTrigger } from '@idux/cdk/overlay' | ||
|
||
import { defineComponent, onMounted, onUpdated, PropType, unref } from 'vue' | ||
import { mount } from '@vue/test-utils' | ||
import { IxButton } from '@idux/components' | ||
import { useOverlay } from '../src/useOverlay' | ||
|
||
const defaultOverlayOptions: OverlayOptions = { | ||
placement: 'bottom', | ||
scrollStrategy: 'reposition', | ||
trigger: 'click', | ||
offset: [0, 0], | ||
hideDelay: 1000, | ||
showDelay: 1000, | ||
} | ||
|
||
describe('useOverlay.ts', () => { | ||
let options: OverlayOptions | ||
let timer: (delay?: number) => Promise<void> | ||
|
||
beforeEach(() => { | ||
options = { ...defaultOverlayOptions } | ||
timer = (delay = 0) => { | ||
return new Promise<void>(resolve => { | ||
setTimeout(resolve, delay) | ||
}) | ||
} | ||
}) | ||
|
||
test('init work', () => { | ||
const instance = useOverlay(options) | ||
expect(instance).toBeDefined() | ||
}) | ||
|
||
test('visible work', async () => { | ||
const TestComponent = defineComponent({ | ||
setup() { | ||
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents, visibility, show, hide } = useOverlay( | ||
options, | ||
) | ||
|
||
onMounted(initialize) | ||
|
||
const handleClick = () => { | ||
unref(visibility) ? hide(true) : show(true) | ||
} | ||
|
||
return { overlayRef, triggerRef, triggerEvents, overlayEvents, handleClick } | ||
}, | ||
template: ` | ||
<button id="trigger" ref="triggerRef" @click="triggerEvents.onClick">Trigger</button> | ||
<button id="immediate" @click="handleClick">Immediate Toggle</button> | ||
<div id="overlay" ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter" @mouseleave="overlayEvents.onMouseLeave">Overlay</div> | ||
`, | ||
}) | ||
const wrapper = mount(TestComponent) | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;') | ||
|
||
await wrapper.get('#trigger').trigger('click') | ||
await timer(1000) | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;') | ||
|
||
await wrapper.get('#trigger').trigger('click') | ||
await timer(1000) | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;') | ||
|
||
await wrapper.get('#immediate').trigger('click') | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;') | ||
|
||
await wrapper.get('#immediate').trigger('click') | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;') | ||
}) | ||
|
||
test('component trigger work', async () => { | ||
const TestComponent = defineComponent({ | ||
components: { IxButton }, | ||
setup() { | ||
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents } = useOverlay({ | ||
...options, | ||
showDelay: 0, | ||
hideDelay: 0, | ||
}) | ||
|
||
onMounted(initialize) | ||
|
||
return { overlayRef, triggerRef, triggerEvents, overlayEvents } | ||
}, | ||
template: ` | ||
<ix-button id="trigger" ref="triggerRef" @click="triggerEvents.onClick">Trigger</ix-button> | ||
<div id="overlay" ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter" @mouseleave="overlayEvents.onMouseLeave">Overlay</div> | ||
`, | ||
}) | ||
const wrapper = mount(TestComponent) | ||
await wrapper.get('#trigger').trigger('click') | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;') | ||
|
||
await wrapper.get('#trigger').trigger('click') | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;') | ||
}) | ||
|
||
test('destroy work', async () => { | ||
const TestComponent = defineComponent({ | ||
setup() { | ||
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents, destroy } = useOverlay({ | ||
...options, | ||
showDelay: 0, | ||
hideDelay: 0, | ||
}) | ||
|
||
onMounted(initialize) | ||
|
||
const handleClick = () => { | ||
destroy() | ||
console.log('destroy') | ||
} | ||
|
||
return { overlayRef, triggerRef, triggerEvents, overlayEvents, handleClick } | ||
}, | ||
template: ` | ||
<button id="trigger" ref="triggerRef" @click="triggerEvents.onClick">Trigger</button> | ||
<button id="destroy" @click="handleClick">Destroy</button> | ||
<div id="overlay" ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter" @mouseleave="overlayEvents.onMouseLeave">Overlay</div> | ||
`, | ||
}) | ||
const wrapper = mount(TestComponent) | ||
|
||
const log = jest.spyOn(console, 'log') | ||
await wrapper.get('#destroy').trigger('click') | ||
expect(log).toBeCalled() | ||
}) | ||
|
||
test('update work', async () => { | ||
const TestComponent = defineComponent({ | ||
components: { IxButton }, | ||
setup() { | ||
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents, update } = useOverlay(options) | ||
|
||
onMounted(initialize) | ||
|
||
const handleClick = () => { | ||
update({ showDelay: 0 }) | ||
} | ||
|
||
return { overlayRef, triggerRef, triggerEvents, overlayEvents, handleClick } | ||
}, | ||
template: ` | ||
<button id="trigger" ref="triggerRef" @click="triggerEvents.onClick">Trigger</button> | ||
<button id="update" @click="handleClick">Update</button> | ||
<div id="overlay" ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter" @mouseleave="overlayEvents.onMouseLeave">Overlay</div> | ||
`, | ||
}) | ||
const wrapper = mount(TestComponent) | ||
|
||
await wrapper.get('#trigger').trigger('click') | ||
await timer(1000) | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;') | ||
|
||
await wrapper.get('#trigger').trigger('click') | ||
await timer(1000) | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;') | ||
|
||
await wrapper.get('#update').trigger('click') | ||
await wrapper.get('#trigger').trigger('click') | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;') | ||
}) | ||
|
||
test('trigger work', async () => { | ||
const TestComponent = defineComponent({ | ||
components: { IxButton }, | ||
props: { | ||
trigger: { | ||
type: String as PropType<OverlayTrigger>, | ||
default: 'click', | ||
}, | ||
}, | ||
setup(props) { | ||
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents, update } = useOverlay({ | ||
...options, | ||
showDelay: 0, | ||
hideDelay: 0, | ||
trigger: props.trigger, | ||
}) | ||
|
||
onMounted(initialize) | ||
|
||
onUpdated(() => { | ||
update({ trigger: props.trigger }) | ||
}) | ||
|
||
return { overlayRef, triggerRef, triggerEvents, overlayEvents } | ||
}, | ||
template: ` | ||
<button id="trigger" ref="triggerRef" @focus='triggerEvents.onFocus' @blur='triggerEvents.onBlur' @mouseenter='triggerEvents.onMouseEnter' @mouseleave='triggerEvents.onMouseLeave' @click="triggerEvents.onClick">Trigger</button> | ||
<div id="overlay" ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter" @mouseleave="overlayEvents.onMouseLeave">Overlay</div> | ||
`, | ||
}) | ||
|
||
const wrapper = mount(TestComponent) | ||
await wrapper.get('#trigger').trigger('click') | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;') | ||
await wrapper.get('#trigger').trigger('click') | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;') | ||
|
||
await wrapper.get('#overlay').trigger('mouseleave') | ||
|
||
await wrapper.setProps({ trigger: 'focus' }) | ||
await wrapper.get('#trigger').trigger('focus') | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;') | ||
await wrapper.get('#trigger').trigger('focus') | ||
|
||
await wrapper.get('#trigger').trigger('blur') | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;') | ||
|
||
await wrapper.setProps({ trigger: 'hover' }) | ||
await wrapper.get('#trigger').trigger('mouseenter') | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;') | ||
await wrapper.get('#trigger').trigger('mouseleave') | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;') | ||
}) | ||
|
||
test('hover overlay work', async () => { | ||
const TestComponent = defineComponent({ | ||
components: { IxButton }, | ||
setup() { | ||
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents } = useOverlay({ | ||
...options, | ||
trigger: 'hover', | ||
visible: true, | ||
allowEnter: true, | ||
showDelay: 0, | ||
hideDelay: 0, | ||
}) | ||
|
||
onMounted(initialize) | ||
|
||
return { overlayRef, triggerRef, triggerEvents, overlayEvents } | ||
}, | ||
template: ` | ||
<button id="trigger" ref="triggerRef" @mouseenter='triggerEvents.onMouseEnter' @mouseleave='triggerEvents.onMouseLeave'>Trigger</button> | ||
<div id="overlay" ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter" @mouseleave="overlayEvents.onMouseLeave">Overlay</div> | ||
`, | ||
}) | ||
const wrapper = mount(TestComponent) | ||
await wrapper.get('#overlay').trigger('mouseenter') | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: block;') | ||
|
||
await wrapper.get('#overlay').trigger('mouseleave') | ||
expect(wrapper.get('#overlay').attributes('style')).toContain('display: none;') | ||
}) | ||
|
||
test('arrow work', async () => { | ||
const TestComponent = defineComponent({ | ||
components: { IxButton }, | ||
setup() { | ||
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents, arrowRef } = useOverlay({ | ||
...options, | ||
showDelay: 0, | ||
showArrow: true, | ||
}) | ||
|
||
onMounted(initialize) | ||
|
||
return { overlayRef, triggerRef, triggerEvents, overlayEvents, arrowRef } | ||
}, | ||
template: ` | ||
<button id="trigger" ref="triggerRef" @click="triggerEvents.onClick">Trigger</button> | ||
<div id="overlay" ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter" @mouseleave="overlayEvents.onMouseLeave">Overlay | ||
<div ref="arrowRef" id='arrow'></div> | ||
</div> | ||
`, | ||
}) | ||
const wrapper = mount(TestComponent) | ||
await wrapper.get('#trigger').trigger('click') | ||
expect(wrapper.get('#arrow')).toBeDefined() | ||
}) | ||
|
||
// todo global scroll | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<template> | ||
<ix-button | ||
ref="triggerRef" | ||
@click="triggerEvents.onClick" | ||
@mouseenter="triggerEvents.onMouseEnter" | ||
@mouseleave="triggerEvents.onMouseLeave" | ||
@focus="triggerEvents.onFocus" | ||
@blur="triggerEvents.onBlur" | ||
>Click</ix-button | ||
> | ||
<ix-button @click="handleClick">Update</ix-button> | ||
<teleport to="body"> | ||
<div ref="overlayRef" @mouseenter="overlayEvents.onMouseEnter()" @mouseleave="overlayEvents.onMouseLeave()"> | ||
tooltip | ||
<div ref="arrowRef"></div> | ||
</div> | ||
</teleport> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent, onMounted } from 'vue' | ||
import { useOverlay } from '@idux/cdk/overlay' | ||
export default defineComponent({ | ||
name: 'Basic', | ||
setup() { | ||
const { initialize, overlayRef, triggerRef, triggerEvents, overlayEvents, update, arrowRef } = useOverlay({ | ||
visible: false, | ||
trigger: 'click', | ||
placement: 'bottom', | ||
scrollStrategy: 'reposition', | ||
offset: [5, 5], | ||
showDelay: 0, | ||
hideDelay: 1000, | ||
allowEnter: true, | ||
}) | ||
onMounted(initialize) | ||
const handleClick = () => { | ||
update({ trigger: 'focus' }) | ||
} | ||
return { overlayRef, triggerRef, triggerEvents, overlayEvents, handleClick, arrowRef } | ||
}, | ||
}) | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
--- | ||
order: 0 | ||
title: | ||
zh: 基本使用 | ||
en: Basic usage | ||
--- | ||
|
||
## zh | ||
|
||
- 如何创建一个浮层 | ||
- 如何初始化浮层 | ||
- 如何更新浮层 | ||
- 如何在模板中绑定对应的事件 | ||
|
||
另外,在组件中使用当前cdk,按照规范请配套使用组件Portal. | ||
|
||
## en | ||
|
||
- How to create an overlay | ||
- How to initialize the overlay | ||
- How to update the configuration of the overlay | ||
- How to bind the events in the template | ||
|
||
By the way, to use the cdk in components, please use the component Portal according to the specification. | ||
|
||
## demo |
Oops, something went wrong.