implement relatedTarget retargeting

This commit is contained in:
Daniel Freedman
2016-08-15 14:54:56 -07:00
parent 9bc47b924d
commit 6ad6718231
4 changed files with 116 additions and 25 deletions

View File

@@ -791,6 +791,33 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
keypress: true
};
function pathComposer(startNode, composed) {
var composedPath = [];
var current = startNode;
while (current) {
composedPath.push(current);
var contents = current.getDestinationInsertionPoints &&
current.getDestinationInsertionPoints();
if (contents && contents.length) {
for (var i = 0; i < contents.length - 1; i++) {
composedPath.push(contents[i]);
}
current = contents[contents.length - 1];
} else if (current.assignedSlot) {
current = current.assignedSlot;
} else if (current.host && composed) {
current = current.host;
} else {
current = current.parentNode;
}
}
// event composedPath includes window when composed = true in most recent native implementations
if (composedPath[composedPath.length - 1] === document) {
composedPath.push(window);
}
return composedPath;
}
var EventMixin = {
__patched: 'Event',
@@ -804,30 +831,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
composedPath() {
if (!this.__composedPath) {
var composedPath = [];
var current = this.__target;
while (current) {
composedPath.push(current);
var contents = current.getDestinationInsertionPoints &&
current.getDestinationInsertionPoints();
if (contents && contents.length) {
for (var i = 0; i < contents.length - 1; i++) {
composedPath.push(contents[i]);
}
current = contents[contents.length - 1];
} else if (current.assignedSlot) {
current = current.assignedSlot;
} else if (current.host && this.composed) {
current = current.host;
} else {
current = current.parentNode;
}
}
// event composedPath includes window when composed = true in most recent native implementations
if (composedPath[composedPath.length - 1] === document) {
composedPath.push(window);
}
this.__composedPath = composedPath;
this.__composedPath = pathComposer(this.__target, this.composed);
}
return this.__composedPath;
},
@@ -846,6 +850,32 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
}
},
get relatedTarget() {
if (!this.__relatedTarget) {
return null;
}
if (!this.__relatedTargetComposedPath) {
this.__relatedTargetComposedPath = pathComposer(this.__relatedTarget, true);
}
var path = this.__relatedTargetComposedPath;
var currentRoot = this.target.getRootNode();
var rt = null;
var idx = path.indexOf(currentRoot);
if (idx === -1) {
var targetPath = this.composedPath();
var tidx = targetPath.indexOf(currentRoot) + 1;
while(idx === -1 && tidx < (targetPath.length - 1)) {
idx = path.indexOf(targetPath[++tidx]);
}
} else {
idx--;
}
rt = path[idx];
while(idx >= 0 && rt && !rt.shadowRoot) {
rt = path[--idx];
}
return rt;
},
stopPropagation() {
Event.prototype.stopPropagation.call(this);
this.__propagationStopped = true;
@@ -916,7 +946,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
e.__proto__ = proto;
}
}
if (e.composedPath().indexOf(this) > -1) {
if (e.composedPath().indexOf(this) > -1 && e.target !== e.relatedTarget) {
return fn(e);
} else {
e.stopImmediatePropagation();

View File

@@ -39,6 +39,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
'unit/case-map.html',
'unit/attributes.html',
'unit/async.html',
'unit/shady-events.html'
];
// http://eddmann.com/posts/cartesian-product-in-javascript/

View File

@@ -0,0 +1,40 @@
<!doctype html>
<template id="a">
<div>Hi</div>
</template>
<template id="b">
<div>Hello</div>
</template>
<template id="c">
<x-inner id="one"></x-inner>
<x-inner id="two"></x-inner>
</template>
<x-outer id="outer"></x-outer>
<x-other id="other"></x-other>
<script>
function shadowMaker(template) {
return class extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
this.shadowRoot.appendChild(document.importNode(template.content, true));
}
}
}
customElements.define('x-inner', shadowMaker(document.querySelector('#b')));
customElements.define('x-other', shadowMaker(document.querySelector('#a')));
customElements.define('x-outer', shadowMaker(document.querySelector('#c')));
function fire() {
var inner = outer.shadowRoot.firstElementChild;
var relatedTarget = outer.shadowRoot.querySelector('#two').shadowRoot.firstElementChild;
// var target = other.shadowRoot.firstElementChild;
var target = outer.shadowRoot.querySelector('#one');
target.dispatchEvent(new MouseEvent('foo', {bubbles: true, composed: true, relatedTarget}));
}
outer.shadowRoot.addEventListener('foo', e => {
console.log(e.target, e.relatedTarget)
console.log(e.composedPath());
});
</script>

View File

@@ -212,6 +212,26 @@ suite('ShadyDOM event patching', function() {
assert.equal(el.events.length, 1);
assert.equal(el.events[0], el.$.child);
});
test('composed relatedTarget retargets', function() {
var els = fixture('relatedtarget');
var a = els[0];
var b = els[1];
var ev = new MouseEvent('foo', {bubbles: true, composed: true, relatedTarget: b.$.child});
ev.__composed = true;
a.$.child.dispatchEvent(ev);
assert.property(a, 'event');
assert.deepEqual(a.event, {target: a, relatedTarget: b});
});
test('events do not fire if relatedtarget and target are the same node after retargeting', function() {
var els = fixture('relatedtarget');
var a = els[0];
var ev = new MouseEvent('foo', {bubbles: true, composed: true, relatedTarget: a.$.child});
ev.__composed = true;
a.dispatchEvent(ev);
assert.notProperty(a, 'event');
});
});
</script>
</body>