migi
Version:
A JavaScript MVVM on JSX
426 lines (421 loc) • 15.9 kB
JavaScript
import Event from './Event';
import sort from './sort';
import hash from './hash';
import matchHash from './matchHash';
import matchUtil from './matchUtil';
// names,classes,ids为从当前节点开始往上的列表
// style为jaw传入的总样式对象
// virtualDom当前传入的VirtualDom对象
// first为初始化时第一次
function match(names, classes, ids, style, virtualDom, first) {
var res = [];
matchSel(names.length - 1, names, classes, ids, style.default, virtualDom, res, first);
// 如果有media query
if(style.media) {
style.media.forEach(function(media) {
var match = false;
media.query.forEach(function(qlist) {
// 中一个即命中不再往下匹配
if(match) {
return;
}
for(var i = 0, len = qlist.length; i < len; i++) {
var item = qlist[i];
// Array/String类型标明是否有值,目前只支持Array
if(Array.isArray(item)) {
var k = item[0].replace(/^-[a-z]+-/i, '').replace(/^mso-/i, '').toLowerCase();
var v = item[1];
// 只支持px单位
if(/(px|\d)$/.test(v)) {
v = v.replace(/px$/, '');
switch(k) {
case 'width':
case 'height':
var cur = getCur(k);
if(cur == v) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
return;
}
break;
case 'min-width':
case 'max-width':
case 'min-height':
case 'max-height':
var cur = getCur(k.slice(4));
if(k.indexOf('min-') == 0) {
if(cur >= v) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
return;
}
}
else {
if(cur <= v) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
return;
}
}
break;
case 'device-width':
case 'device-height':
var cur = window.screen[k.slice(7)];
if(cur == v) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
}
break;
case 'min-device-width':
case 'min-device-height':
case 'max-device-width':
case 'max-device-height':
var cur = window.screen[k.slice(11)];
if(k.indexOf('min-') == 0) {
if(cur >= v) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
return;
}
}
else {
if(cur <= v) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
return;
}
}
break;
case 'aspect-ratio':
var w = getCur('width');
var h = getCur('height');
var cur = w / h;
var val = v.split('/');
val = val[0] / val[1];
if(cur == val) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
return;
}
break;
case 'min-aspect-ratio':
case 'max-aspect-ratio':
var w = getCur('width');
var h = getCur('height');
var cur = w / h;
var val = v.split('/');
val = val[0] / val[1];
if(k.indexOf('min-') == 0) {
if(cur >= v) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
return;
}
}
else {
if(cur <= v) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
return;
}
}
break;
case 'device-aspect-ratio':
var w = window.screen.width;
var h = window.screen.height;
var cur = w / h;
var val = v.split('/');
val = val[0] / val[1];
if(cur == val) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
return;
}
break;
case 'min-device-aspect-ratio':
case 'max-device-aspect-ratio':
var w = window.screen.width;
var h = window.screen.height;
var cur = w / h;
var val = v.split('/');
val = val[0] / val[1];
if(k.indexOf('min-') == 0) {
if(cur >= v) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
return;
}
}
else {
if(cur <= v) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
return;
}
}
break;
case 'device-pixel-ratio':
var cur = window.devicePixelRatio;
if(cur == v) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
return;
}
break;
case 'min-device-pixel-ratio':
case 'max-device-pixel-ratio':
var cur = window.devicePixelRatio;
if(k.indexOf('min-') == 0) {
if(cur >= v) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
return;
}
}
else {
if(cur <= v) {
match = true;
matchSel(names.length - 1, names, classes, ids, media.style, virtualDom, res, first);
return;
}
}
break;
}
}
}
}
});
});
// 窗口resize时重新匹配@media query
if(first) {
var timeout;
function resize() {
if(timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(function() {
hash.get(virtualDom.__uid).__updateStyle();
}, 100);
}
window.addEventListener('resize', resize);
matchHash.add(virtualDom.__uid, resize);
}
}
sort(res, function(a, b) {
var pa = a[2];
var pb = b[2];
// 引用相等比较出现顺序
if(pa == pb) {
return a[0] > b[0];
}
// 优先级不相等
for(var i = 0; i < 3; i++) {
if(pa[i] != pb[i]) {
return pa[i] > pb[i];
}
}
// 优先级相等比较出现顺序
return a[0] > b[0];
});
var s = '';
res.forEach(function(item) {
s += item[1] + ';';
});
return s;
}
// 从底部往上匹配,即.a .b这样的选择器是.b->.a逆序对比
// 过程中只要不匹配就跳出,i从最大到0
function matchSel(i, names, classes, ids, style, virtualDom, res, first, isChild) {
var combo = matchUtil.combo(classes[i], names[i], ids[i], style);
for(var j = 0, len = combo.length; j < len; j++) {
var k = combo[j];
if(style.hasOwnProperty(k)) {
var item = style[k];
// 还未到根节点继续匹配
if(i) {
matchSel(i - 1, names, classes, ids, item, virtualDom.parent, res, first);
// 多层级时需递归所有层级组合,如<div><p><span>对应div span{}的样式时,并非一一对应
if(!isChild) {
for(var l = i - 2; l >= 0; l--) {
matchSel(l, names, classes, ids, item, virtualDom.parent, res, first);
}
}
}
// 将当前层次的值存入
if(item.hasOwnProperty('_v')) {
dealStyle(res, item);
}
// 首次进入处理:伪类
if(first && item.hasOwnProperty('_:')) {
item['_:'].forEach(function(pseudoItem) {
pseudoItem[0].forEach(function(pseudo) {
var uid = virtualDom.__uid;
switch(pseudo) {
case 'hover':
function onHover() {
// 因为vd可能destroy导致被回收,所以每次动态从hash中取当前的vd
hash.get(uid).__hover = true;
hash.get(uid).__updateStyle();
}
function outHover() {
hash.get(uid).__hover = false;
hash.get(uid).__updateStyle();
}
function cb() {
virtualDom.element.addEventListener('mouseenter', onHover);
virtualDom.element.addEventListener('mouseleave', outHover);
}
// 可能由DOMDiff发起,此时已经在DOM上了
if(virtualDom.__dom) {
cb();
}
else {
virtualDom.once(Event.DOM, cb);
}
// 记录缓存当destryo时移除
virtualDom.__onHover = onHover;
virtualDom.__outHover = outHover;
break;
case 'active':
function onActive() {
// 因为vd可能destroy导致被回收,所以每次动态从hash中取当前的vd
hash.get(uid).__active = true;
hash.get(uid).__updateStyle();
}
function outActive() {
hash.get(uid).__active = false;
hash.get(uid).__updateStyle();
}
function cb2() {
virtualDom.element.addEventListener('mousedown', onActive);
// 鼠标弹起捕获body,因为可能会移出元素后再弹起,且事件被shadow化阻止冒泡了
window.addEventListener('mouseup', outActive, true);
// touchend也失焦
window.addEventListener('touchend', outActive, true);
// touchcancel也失焦
window.addEventListener('touchcancel', outActive, true);
// window失焦时也需判断
window.addEventListener('blur', outActive);
// drag结束时也需判断
window.addEventListener('dragend', outActive);
}
// 可能由DOMDiff发起,此时已经在DOM上了
if(virtualDom.__dom) {
cb2();
}
else {
virtualDom.once(Event.DOM, cb2);
}
// 对window的侦听需要在destroy后移除,先记录下来
matchHash.add(uid, outActive);
break;
}
});
});
}
// :和[可能同时存在,也可能分开
if(item.hasOwnProperty('_:') || item.hasOwnProperty('_[')) {
var item2;
// 有:伪类时,检查是否满足伪类情况,全部满足后追加样式
if(item.hasOwnProperty('_:')) {
item2 = item['_:'];
item2.forEach(function(pseudoItem) {
var isMatch = matchUtil.pseudo(pseudoItem[0], virtualDom, k);
if(isMatch) {
item2 = pseudoItem[1];
// 同普通匹配一样
if(i) {
matchSel(i - 1, names, classes, ids, item2, virtualDom.parent, res, first);
}
if(item2.hasOwnProperty('_v')) {
dealStyle(res, item2);
}
}
});
}
// 有:[属性时,检查是否满足伪类情况,全部满足后追加样式
function inAttr(item) {
if(item && item.hasOwnProperty('_[')) {
var item2 = item['_['];
item2.forEach(function(attrItem) {
var isMatch = matchUtil.attr(attrItem[0], virtualDom);
if(isMatch) {
item2 = attrItem[1];
// 同普通匹配一样
if(i) {
matchSel(i - 1, names, classes, ids, item2, virtualDom.parent, res, first);
}
if(item2.hasOwnProperty('_v')) {
dealStyle(res, item2);
}
}
});
}
}
inAttr(item);
inAttr(item2);
}
// 父子选择器
if(item.hasOwnProperty('_>')) {
var item2 = item['_>'];
matchSel(i - 1, names, classes, ids, item2, virtualDom.parent, res, false, isChild);
}
// 相邻兄弟选择器
if(item.hasOwnProperty('_+')) {
var item2 = item['_+'];
var prev = virtualDom.prev();
if(prev) {
Object.keys(item2).forEach(function(k) {
if(matchUtil.nci(k, prev)) {
return;
}
// 将当前层次的值存入
if(item2[k].hasOwnProperty('_v')) {
dealStyle(res, item2[k]);
}
matchSel(i - 1, names, classes, ids, item2[k], virtualDom.parent, res, false, isChild);
});
}
}
// 兄弟选择器
if(item.hasOwnProperty('_~')) {
var item2 = item['_~'];
var prevAll = virtualDom.prevAll();
if(prevAll.length) {
var hasSibiling = false;
for(var j = prevAll.length - 1; j >= 0; j--) {
var prev = prevAll[j];
Object.keys(item2).forEach(function(k) {
if(matchUtil.nci(k, prev)) {
return;
}
// 将当前层次的值存入
if(item2[k].hasOwnProperty('_v')) {
dealStyle(res, item2[k]);
}
hasSibiling = true;
matchSel(i - 1, names, classes, ids, item2[k], virtualDom.parent, res, false, isChild);
});
// 一旦前方出现一次,即说明命中兄弟选择器,可以跳出继续判断下去的循环
if(hasSibiling) {
break;
}
}
}
}
}
}
}
function dealStyle(res, item) {
item._v.forEach(function(style) {
style[2] = item._p;
res.push(style);
});
}
function getCur(k) {
var key = k.charAt(0).toUpperCase() + k.slice(1);
return window['inner' + key]
|| document.documentElement['client' + key]
|| document.body['client' + key];
}
export default match;