commit
c99209f18a
|
@ -286,4 +286,98 @@ describe('PortalComponent Test', () => {
|
|||
dispatchChangeEvent(inputRef.current, 'test');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
||||
it('portal场景下,portal下元素点击事件冒泡到父元素', () => {
|
||||
class Dialog extends Inula.Component {
|
||||
node;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.node = window.document.createElement('div');
|
||||
window.document.body.appendChild(this.node);
|
||||
}
|
||||
|
||||
render() {
|
||||
return Inula.createPortal(this.props.children, this.node);
|
||||
}
|
||||
}
|
||||
|
||||
const fn = jest.fn();
|
||||
const subRef = Inula.createRef();
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div onClick={fn}>
|
||||
<Dialog>
|
||||
<div ref={subRef}/>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Inula.render(<App />, container);
|
||||
Inula.act(() => {
|
||||
subRef.current.dispatchEvent(new Event('click', { bubbles: true }));
|
||||
});
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('portal嵌套场景下事件委托', () => {
|
||||
class Dialog extends Inula.Component {
|
||||
node;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.node = window.document.createElement('div');
|
||||
window.document.body.appendChild(this.node);
|
||||
}
|
||||
|
||||
render() {
|
||||
return Inula.createPortal(this.props.children, this.node);
|
||||
}
|
||||
}
|
||||
|
||||
const fn = jest.fn();
|
||||
const inputRef = Inula.createRef();
|
||||
let value = '';
|
||||
const onChange = (evt) => {
|
||||
value = evt.target.value;
|
||||
}
|
||||
|
||||
let showSubPortal = () => {};
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Dialog>
|
||||
<input onChange={fn}></input>
|
||||
<Sub />
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Sub() {
|
||||
const [show, setShow] = Inula.useState(false);
|
||||
showSubPortal = setShow;
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
show &&
|
||||
<Dialog>
|
||||
<input ref={inputRef} onChange={onChange}></input>
|
||||
</Dialog>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Inula.render(<App />, container);
|
||||
Inula.act(() => {
|
||||
showSubPortal(true);
|
||||
});
|
||||
dispatchChangeEvent(inputRef.current, 'test');
|
||||
expect(value).toEqual('test');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
/**
|
||||
* 事件绑定实现,分为绑定委托事件和非委托事件
|
||||
*/
|
||||
import { allDelegatedInulaEvents, simulatedDelegatedEvents } from './EventHub';
|
||||
import { allDelegatedInulaEvents, portalDefaultDelegatedEvents, simulatedDelegatedEvents } from './EventHub';
|
||||
import { isDocument } from '../dom/utils/Common';
|
||||
import { getNearestVNode, getNonDelegatedListenerMap } from '../dom/DOMInternalKeys';
|
||||
import { asyncUpdates, runDiscreteUpdates } from '../renderer/TreeBuilder';
|
||||
|
@ -89,6 +89,13 @@ export function listenSimulatedDelegatedEvents(root: VNode) {
|
|||
}
|
||||
}
|
||||
|
||||
// portal绑定默认事件
|
||||
export function listenPortalEvents(root: VNode) {
|
||||
for (let i = 0; i < portalDefaultDelegatedEvents.length; i++) {
|
||||
lazyDelegateOnRoot(root, portalDefaultDelegatedEvents[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// 通过inula事件名获取到native事件名
|
||||
function getNativeEvtName(inulaEventName, capture) {
|
||||
let nativeName;
|
||||
|
|
|
@ -16,6 +16,13 @@
|
|||
// 需要委托的inula事件和原生事件对应关系
|
||||
export const allDelegatedInulaEvents = new Map();
|
||||
|
||||
/**
|
||||
* Portal根节点默认绑定事件,解决常见事件无法冒泡到parent vnode的问题
|
||||
* 例如:parent vNode节点绑定了mousedown事件,子节点为portal节点,子节点下元素未绑定mousedown事件
|
||||
* 此时,点击portal下子元素,mousedown事件无法冒泡到parentNode
|
||||
*/
|
||||
export const portalDefaultDelegatedEvents = ['onMouseDown', 'onMouseUp', 'onKeyDown', 'onKeyUp', 'onFocus', 'onBlur', 'onClick'];
|
||||
|
||||
// 模拟委托事件,不冒泡事件需要利用其他事件来触发冒泡过程
|
||||
export const simulatedDelegatedEvents = ['onMouseEnter', 'onMouseLeave'];
|
||||
// 所有委托的原生事件集合
|
||||
|
|
|
@ -244,17 +244,22 @@ export function calcStartUpdateVNode(treeRoot: VNode) {
|
|||
// 在局部更新时,从上到下恢复父节点的context和PortalStack
|
||||
function recoverTreeContext(vNode: VNode) {
|
||||
const contextProviders: VNode[] = [];
|
||||
const portalRoots: VNode[] = [];
|
||||
let parent = vNode.parent;
|
||||
while (parent !== null) {
|
||||
if (parent.tag === ContextProvider) {
|
||||
contextProviders.unshift(parent);
|
||||
} else if (parent.tag === DomPortal) {
|
||||
portalRoots.unshift(parent);
|
||||
}
|
||||
if (parent.tag === DomPortal) {
|
||||
pushCurrentRoot(parent);
|
||||
}
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
portalRoots.forEach(node => {
|
||||
pushCurrentRoot(node);
|
||||
});
|
||||
contextProviders.forEach(node => {
|
||||
setContext(node, node.props.value);
|
||||
});
|
||||
|
|
|
@ -17,11 +17,12 @@ import type { VNode } from '../Types';
|
|||
import { resetNamespaceCtx, setNamespaceCtx } from '../ContextSaver';
|
||||
import { createChildrenByDiff } from '../diff/nodeDiffComparator';
|
||||
import { popCurrentRoot, pushCurrentRoot } from '../RootStack';
|
||||
import { listenSimulatedDelegatedEvents } from '../../event/EventBinding';
|
||||
import { listenPortalEvents, listenSimulatedDelegatedEvents } from '../../event/EventBinding';
|
||||
|
||||
export function bubbleRender(processing: VNode) {
|
||||
resetNamespaceCtx(processing);
|
||||
listenSimulatedDelegatedEvents(processing);
|
||||
listenPortalEvents(processing);
|
||||
popCurrentRoot();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue