diff --git a/src/css.js b/src/css.js index ba5561add..7876d0c7f 100644 --- a/src/css.js +++ b/src/css.js @@ -61,24 +61,22 @@ const rnsvgCssSelectAdapter = baseCssAdapter(rnsvgCssSelectAdapterMin); * * @param {Object} document to select elements from * @param {String} selectors CSS selector(s) string - * @return {Array} null if no elements matched + * @return {Array} */ function querySelectorAll(document, selectors) { - const matchedEls = cssSelect(selectors, document, cssSelectOpts); - - return matchedEls.length > 0 ? matchedEls : null; + return cssSelect(selectors, document, cssSelectOpts); } const cssSelectOpts = { xmlMode: true, adapter: rnsvgCssSelectAdapter, }; -function specificity(simpleSelector) { +function specificity(selector) { let A = 0; let B = 0; let C = 0; - simpleSelector.children.each(function walk(node) { + selector.children.each(function walk(node) { switch (node.type) { case 'SelectorList': case 'Selector': @@ -134,49 +132,39 @@ function specificity(simpleSelector) { * Flatten a CSS AST to a selectors list. * * @param {Object} cssAst css-tree AST to flatten - * @return {Array} selectors + * @param {Array} selectors */ -function flattenToSelectors(cssAst) { - const selectors = []; - +function flattenToSelectors(cssAst, selectors) { csstree.walk(cssAst, { visit: 'Rule', - enter(node) { - if (node.type !== 'Rule') { + enter(rule) { + const { type, prelude } = rule; + if (type !== 'Rule') { return; } - const atrule = this.atrule; - const rule = node; - - node.prelude.children.each((selectorNode, selectorItem) => { - const selector = { - item: selectorItem, - atrule: atrule, - rule: rule, - pseudos: [], - }; - - selectorNode.children.each( - (selectorChildNode, selectorChildItem, selectorChildList) => { - if ( - selectorChildNode.type === 'PseudoClassSelector' || - selectorChildNode.type === 'PseudoElementSelector' - ) { - selector.pseudos.push({ - item: selectorChildItem, - list: selectorChildList, - }); - } - }, - ); - - selectors.push(selector); + prelude.children.each(({ children }, item) => { + const pseudos = []; + selectors.push({ + item, + atrule, + rule, + pseudos, + }); + children.each(({ type: childType }, pseudoItem, list) => { + if ( + childType === 'PseudoClassSelector' || + childType === 'PseudoElementSelector' + ) { + pseudos.push({ + item: pseudoItem, + list, + }); + } + }); }); }, }); - - return selectors; } /** @@ -264,20 +252,16 @@ function compareSpecificity(aSpecificity, bSpecificity) { /** * Compare two simple selectors. * - * @param {Object} aSimpleSelectorNode Simple selector A - * @param {Object} bSimpleSelectorNode Simple selector B + * @param {Object} selectorA Simple selector A + * @param {Object} selectorB Simple selector B * @return {Number} Score of selector A compared to selector B */ -function compareSimpleSelectorNode(aSimpleSelectorNode, bSimpleSelectorNode) { - const aSpecificity = specificity(aSimpleSelectorNode), - bSpecificity = specificity(bSimpleSelectorNode); +function bySelectorSpecificity(selectorA, selectorB) { + const aSpecificity = specificity(selectorA.item.data), + bSpecificity = specificity(selectorB.item.data); return compareSpecificity(aSpecificity, bSpecificity); } -function _bySelectorSpecificity(selectorA, selectorB) { - return compareSimpleSelectorNode(selectorA.item.data, selectorB.item.data); -} - /** * Sort selectors stably by their specificity. * @@ -285,98 +269,97 @@ function _bySelectorSpecificity(selectorA, selectorB) { * @return {Array} Stable sorted selectors */ function sortSelectors(selectors) { - return stable(selectors, _bySelectorSpecificity); -} - -/** - * Gets the CSS string of a style element - * - * @param {Object} element style element - * @return {String|Array} CSS string or empty array if no styles are set - */ -function getCssStr(element) { - return element.children || []; + return stable(selectors, bySelectorSpecificity); } function CSSStyleDeclaration(node) { - this.style = node.props.style; - this.properties = new Map(); + const style = { + style: node.props.style, + properties: new Map(), + }; const { styles } = node; if (!styles || styles.length === 0) { - return; + return style; } - let declarations = {}; try { - declarations = csstree.parse(styles, { - context: 'declarationList', - parseValue: false, - }); + csstree + .parse(styles, { + context: 'declarationList', + parseValue: false, + }) + .children.each(({ property, value, important }) => { + try { + setProperty(style, property, csstree.generate(value), important); + } catch (styleError) { + if (styleError.message !== 'Unknown node type: undefined') { + console.warn( + "Warning: Parse error when parsing inline styles, style properties of this element cannot be used. The raw styles can still be get/set using .attr('style').value. Error details: " + + styleError, + ); + } + } + }); } catch (parseError) { console.warn( "Warning: Parse error when parsing inline styles, style properties of this element cannot be used. The raw styles can still be get/set using .attr('style').value. Error details: " + parseError, ); - return; } - declarations.children.each(declaration => { - try { - const { property, value, important } = declaration; - this.setProperty(property, csstree.generate(value), important); - } catch (styleError) { - if (styleError.message !== 'Unknown node type: undefined') { - console.warn( - "Warning: Parse error when parsing inline styles, style properties of this element cannot be used. The raw styles can still be get/set using .attr('style').value. Error details: " + - styleError, - ); - } - } - }); + return style; } -CSSStyleDeclaration.prototype.getProperty = function(propertyName) { - if (typeof propertyName === 'undefined') { - throw Error('1 argument required, but only 0 present.'); - } - - return this.properties.get(propertyName.trim()); -}; - -// writes to properties - /** * Modify an existing CSS property or creates a new CSS property in the declaration block. * + * @param {{properties: *, style: *}} * @param {String} name representing the CSS property name to be modified. * @param {String} [value] containing the new property value. If not specified, treated as the empty string. value must not contain "!important" -- that should be set using the priority parameter. * @param {String} [important] allowing the "important" CSS priority to be set. If not specified, treated as the empty string. * @return {undefined} */ -CSSStyleDeclaration.prototype.setProperty = function(name, value, important) { +function setProperty({ properties, style }, name, value, important) { if (typeof name === 'undefined') { throw Error('propertyName argument required, but only not present.'); } - - const trimmedValue = value.trim(); - const property = { - value: trimmedValue, - important, - }; + const v = value.trim(); const key = name.trim(); - this.properties.set(key, property); - this.style[camelCase(key)] = trimmedValue; + properties.set(key, { + value: v, + important, + }); + style[camelCase(key)] = v; +} - return property; -}; +/** + * Find the closest ancestor of the current element. + * @param node + * @param elemName + * + * @return {?Object} + */ +function closestElem(node, elemName) { + let elem = node; + + while ((elem = elem.parent) && elem.tag !== elemName) {} + + return elem; +} + +function initStyle(selectedEl) { + if (!selectedEl.style) { + if (!selectedEl.props.style) { + selectedEl.props.style = {}; + } + selectedEl.style = CSSStyleDeclaration(selectedEl); + } +} /** * Moves + merges styles from style elements to element styles * * Options - * onlyMatchedOnce (default: true) - * inline only selectors that match once - * * useMqs (default: ['', 'screen']) * what media queries to be used * empty string element for styles outside media queries @@ -391,55 +374,42 @@ CSSStyleDeclaration.prototype.setProperty = function(name, value, important) { * @author strarsis */ const opts = { - onlyMatchedOnce: true, useMqs: ['', 'screen'], usePseudos: [''], }; - -function initStyle(selectedEl) { - if (!selectedEl.style) { - if (!selectedEl.props.style) { - selectedEl.props.style = {}; - } - selectedEl.style = new CSSStyleDeclaration(selectedEl); - } -} - export function inlineStyles(document) { // collect