1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20import { Log } from '../../utils/index'; 21import { watch } from './directive'; 22import { parse, Selector } from '../../src/index'; 23import Element from '../../vdom/Element'; 24import Vm from './index'; 25import { cssType } from './vmOptions'; 26import { 27 FragBlockInterface, 28 isBlock 29} from './compiler'; 30 31interface ContentInterface { 32 type: string; 33 name?: string; 34 action?: string; 35 value?: string; 36 ignoreCase?: boolean; 37} 38 39interface ParamsInterface { 40 id: string | Function; 41 class: string[] | Function, 42 tag: string 43} 44 45interface EvidenceInterface { 46 id: string; 47 ruleDef: ContentInterface[]; 48 style: Record<string, any>; 49 score: number; 50 order: number; 51 priority?: number; 52} 53 54type ListType<T> = Partial<Record<'tagList' | 'idList' | 'classList', T[]>> 55 56type ResStyle<T> = Partial<{resArray: T[], watchValue: T}>; 57 58/** 59 * Support css descendant selector. 60 * @param {cssType } styleOri - Css style. 61 * @return {ListType<EvidenceInterface>} object: {tagList, idList, classList} 62 */ 63export function selector(styleOri: cssType): ListType<EvidenceInterface> { 64 const style = addMetaToStyle(styleOri); 65 const list: ListType<EvidenceInterface> = {}; 66 const keys = Object.keys(style || {}); 67 keys.forEach((key) => { 68 if (style[key] && style[key]._meta) { 69 const ruleDef = style[key]._meta.ruleDef; 70 if (ruleDef) { 71 const rule = ruleDef[ruleDef.length - 1]; 72 const object: EvidenceInterface = { 73 id: rule.type === 'tag' ? rule.name : rule.value, 74 ruleDef: ruleDef, 75 style: style[key], 76 score: getScore(ruleDef), 77 order: keys.indexOf(key) 78 }; 79 if (rule.type === 'tag') { 80 list.tagList = list.tagList || []; 81 list.tagList.push(object); 82 } else if (rule.type === 'attribute' && rule.name === 'id') { 83 list.idList = list.idList || []; 84 list.idList.push(object); 85 } else if (rule.type === 'attribute' && rule.name === 'class') { 86 list.classList = list.classList || []; 87 list.classList.push(object); 88 } else { 89 Log.error(`Invalid ruleDef, t = ${rule.type}, name = ${rule.name}.`); 90 } 91 } 92 } 93 }); 94 return list; 95} 96 97/** 98 * Set descendant style. 99 * @param {ListType<EvidenceInterface>} selector - Descendant selector. 100 * @param {ParamsInterface} params - Param: {id, class, tag...} 101 * @param {Element | FragBlockInterface} parentElement - Parent element object. 102 * @param {Vm} vm - Vm object. 103 * @param {Function} styleFunction - StyleFunction. 104 */ 105export function setDescendantStyle( 106 selector: ListType<EvidenceInterface>, 107 params: Partial<ParamsInterface>, 108 parentElement: Element | FragBlockInterface, 109 vm: Vm, 110 styleFunction: (style: object) => void 111): void { 112 const retStyle = {}; 113 let oldStyle; 114 const applyStyleRes = applyStyle(selector, params, parentElement, vm, undefined); 115 assignStyleByPriority(retStyle, applyStyleRes); 116 styleFunction(retStyle); 117 oldStyle = retStyle; 118 const watchValue = applyStyleRes.watchValue; 119 if (watchValue) { 120 watch(vm, watchValue.value, v => { 121 const applyStyleRes = applyStyle(selector, params, parentElement, vm, v); 122 const retStyle = {}; 123 assignStyleByPriority(retStyle, applyStyleRes); 124 if (oldStyle) { 125 Object.keys(oldStyle).forEach(function(key) { 126 if (!retStyle[key]) { 127 retStyle[key] = ''; 128 } 129 }); 130 } 131 styleFunction(retStyle); 132 oldStyle = retStyle; 133 }); 134 } 135} 136 137/** 138 * Apply style. 139 * @param {ListType<EvidenceInterface>} selector - Descendant selector. 140 * @param {ParamsInterface} params - Param: {id, class, tag...} 141 * @param {Element | FragBlockInterface} parentElement - Parent element object. 142 * @param {Vm} vm - Vm object. 143 * @param {string[]} [classValue] - If has class value. 144 * @return {ResStyle<any>} {resArray, watchValue} 145 */ 146function applyStyle( 147 selector: ListType<EvidenceInterface>, 148 params: Partial<ParamsInterface>, 149 parentElement: Element | FragBlockInterface, 150 vm: Vm, 151 classValue?: string[] 152): ResStyle<any> { 153 const applyStyleRes: ResStyle<any> = {}; 154 applyStyleRes.resArray = []; 155 if (params.id) { 156 const value = isIdFunction(params.id) ? params.id.call(vm, vm) : params.id; 157 const rets = setDescendant(selector.idList, value, parentElement); 158 if (rets) { 159 applyStyleRes.resArray = applyStyleRes.resArray.concat(rets); 160 } 161 } 162 if (!classValue) { 163 if (params.class) { 164 classValue = isClassFunction(params.class) ? params.class.call(vm, vm) : params.class; 165 applyStyleRes.watchValue = isClassFunction(params.class) ? {list: selector.classList, value: params.class} : undefined; 166 } 167 } 168 if (Array.isArray(classValue)) { 169 classValue.forEach(function(value) { 170 const rets = setDescendant(selector.classList, value, parentElement); 171 if (rets) { 172 applyStyleRes.resArray = applyStyleRes.resArray.concat(rets); 173 } 174 }); 175 } else { 176 const rets = setDescendant(selector.classList, classValue, parentElement); 177 if (rets) { 178 applyStyleRes.resArray = applyStyleRes.resArray.concat(rets); 179 } 180 } 181 182 if (params.tag) { 183 const rets = setDescendant(selector.tagList, params.tag, parentElement); 184 if (rets) { 185 applyStyleRes.resArray = applyStyleRes.resArray.concat(rets); 186 } 187 } 188 return applyStyleRes; 189} 190 191/** 192 * Assign style by priority. 193 * @param {Object} retStyle - Returned style. 194 * @param {ResStyle<any>} applyStyleRes - {resArray, watchValue} 195 */ 196function assignStyleByPriority(retStyle: object, applyStyleRes: ResStyle<any>): void { 197 const arr = applyStyleRes.resArray; 198 arr.sort(function(a, b) { 199 if (!a) { 200 return -1; 201 } else if (!b) { 202 return 1; 203 } else { 204 return a.priority > b.priority ? 1 : -1; 205 } 206 }); 207 arr.forEach(function(key) { 208 if (key && key.style) { 209 Object.assign(retStyle, key.style); 210 } 211 }); 212} 213 214/** 215 * Set descendant for Node. 216 * @param {EvidenceInterface} list - Node list to be set priority. 217 * @param {string} value - Value. 218 * @param {Element | FragBlockInterface} parentElement - Parent element. 219 * @return {*} Node list after setting priority 220 */ 221function setDescendant(list: EvidenceInterface[], value: string, parentElement: Element | FragBlockInterface) { 222 if (!list) { 223 return; 224 } 225 const rets = []; 226 for (let i = 0; i < list.length; i++) { 227 if (list[i].id === value) { 228 const ruleDef = list[i].ruleDef; 229 let parent = parentElement; 230 let markElement; 231 for (let j = ruleDef.length - 3; j >= 0; j = j - 2) { 232 const rule = ruleDef[j]; 233 markElement = getElement( 234 rule.type === 'tag' ? rule.type : rule.name, 235 ruleDef[j + 1].type !== 'child', 236 rule.type === 'tag' ? rule.name : rule.value, 237 parent 238 ); 239 if (!markElement) { 240 break; 241 } 242 parent = markElement.parentNode; 243 if (parent === undefined && markElement.ref !== '_documentElement' && global.treeModeParentNode) { 244 parent = global.treeModeParentNode; 245 } 246 } 247 if (markElement) { 248 const ret = list[i]; 249 ret.priority = ret.score + ret.order * 0.01; 250 rets.push(ret); 251 } 252 } 253 } 254 return rets; 255} 256 257/** 258 * Get parent by condition 259 * @param {string} type - Condition type: class id tag. 260 * @param {string} iter - If Can get parent`s parent node. 261 * @param {string} value - Condition value, class value/id value/tag value. 262 * @param {Element | FragBlockInterface} element - ParentNode. 263 * @return {*} ParentNode. 264 */ 265function getElement(type: string, iter: boolean, value: string, element: Element | FragBlockInterface) { 266 if (element === null) { 267 return undefined; 268 } 269 if (isBlock(element)) { 270 element = element.element; 271 } 272 let parentNode = element.parentNode as Element; 273 if (parentNode === null && element.ref !== '_documentElement' && global.treeModeParentNode) { 274 parentNode = global.treeModeParentNode; 275 } 276 // Type : class id tag per. 277 const condition = type === 'class' ? element.classList && element.classList.indexOf(value) !== -1 278 : type === 'id' ? element.id === value : element.type === value; 279 return condition ? element : iter ? getElement(type, iter, value, parentNode) : undefined; 280} 281 282/** 283 * Get score of Node. 284 * @param {ContentInterface[]} ruleDef - Rule. 285 * @return {number} - Score. 286 */ 287function getScore(ruleDef: ContentInterface[]): number { 288 let score = 0; 289 for (let i = 0; i < ruleDef.length; i++) { 290 const rule = ruleDef[i]; 291 if (rule.type === 'tag') { 292 score += 1; 293 } else if (rule.type === 'attribute' && rule.name === 'id') { 294 score += 10000; 295 } else if (rule.type === 'attribute' && rule.name === 'class') { 296 score += 100; 297 } 298 } 299 return score; 300} 301 302/** 303 * Add meta to style. 304 * @param {cssType} classObject - Class style Info. 305 * @return {cssType } Class style Info List. 306 */ 307function addMetaToStyle(classObject: cssType): cssType { 308 const classList = classObject; 309 const classListkey = Object.keys(classList || {}); 310 classListkey.forEach(key => { 311 // Style is not empty and _meta exists. 312 if ( 313 Object.keys(classList[key]).length > 0 && 314 !classList[key]['_meta'] && 315 key !== '.@originalRootEl' && 316 key !== '@KEYFRAMES' && 317 key !== '@MEDIA' && 318 key !== '@FONT-FACE' && 319 key !== '@TRANSITION' 320 ) { 321 const meta: { ruleDef?: Selector[] } = {}; 322 const keys = parse(key); 323 meta.ruleDef = keys[0]; 324 if (meta.ruleDef && meta.ruleDef.length >= 3) { 325 classList[key]['_meta'] = meta; 326 } 327 } 328 }); 329 return classList; 330} 331 332/** 333 * Check if it is a Id function. 334 * @param {Function | string} param - Any type. 335 * @return {boolean} Param is Function or not. 336 */ 337function isIdFunction(param: Function | string): param is Function { 338 const newParam = <Function>param; 339 return newParam.call !== undefined; 340} 341 342/** 343 * Check if it is a class function. 344 * @param {Function | string} param - Any type. 345 * @return {boolean} Param is Function or not. 346 */ 347function isClassFunction(param: Function | string[]): param is Function { 348 const newParam = <Function>param; 349 return newParam.call !== undefined; 350} 351