• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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