UNPKG

substance

Version:

Substance is a JavaScript library for web-based content editing. It provides building blocks for realizing custom text editors and web-based publishing system. It is developed to power our online editing platform [Substance](http://substance.io).

1,117 lines (914 loc) 27.2 kB
import domutils from '../dom/domutils'; import boolbase from './boolbase'; import cssWhat from './css-what'; import nthCheck from './nth-check'; var procedure = new Map([ ["universal", 50], ["tag", 30], ["attribute", 1], ["pseudo", 0], ["descendant", -1], ["child", -1], ["parent", -1], ["sibling", -1], ["adjacent", -1] ]); var sort = sortByProcedure; /* sort the parts of the passed selector, as there is potential for optimization (some types of selectors are faster than others) */ let attributes = new Map([ ['exists', 10], ['equals', 8], ['not', 7], ['start', 6], ['end', 6], ['any', 5], ['hyphen', 4], ['element', 4] ]); function sortByProcedure(arr){ var procs = arr.map(getProcedure); for(var i = 1; i < arr.length; i++){ var procNew = procs[i]; if(procNew < 0) continue; for(var j = i - 1; j >= 0 && procNew < procs[j]; j--){ var token = arr[j + 1]; arr[j + 1] = arr[j]; arr[j] = token; procs[j + 1] = procs[j]; procs[j] = procNew; } } } function getProcedure(token){ var proc = procedure.get(token.type); if(proc === procedure.get('attribute')){ proc = attributes.get(token.action); if(proc === attributes.get('equals') && token.name === "id"){ //prefer ID selectors (eg. #ID) proc = 9; } if(token.ignoreCase){ //ignoreCase adds some overhead, prefer "normal" token //this is a binary operation, to ensure it's still an int proc >>= 1; } } else if(proc === procedure.get('pseudo')){ if(!token.data){ proc = 3; } else if(token.name === "has" || token.name === "contains"){ proc = 0; //expensive in any case } else if(token.name === "matches" || token.name === "not"){ proc = 0; for(var i = 0; i < token.data.length; i++){ //TODO better handling of complex selectors if(token.data[i].length !== 1) continue; var cur = getProcedure(token.data[i][0]); //avoid executing :has or :contains if(cur === 0){ proc = 0; break; } if(cur > proc) proc = cur; } if(token.data.length > 1 && proc > 0) proc -= 1; } else { proc = 1; } } return proc; } var falseFunc = boolbase.falseFunc; //https://github.com/slevithan/XRegExp/blob/master/src/xregexp.js#L469 var reChars = /[-[\]{}()*+?.,\\^$|#\s]/g; function factory(adapter){ function _equals(next, data){ var name = data.name, value = data.value; if(data.ignoreCase){ value = value.toLowerCase(); return function equalsIC(elem){ var attr = adapter.getAttributeValue(elem, name); return attr != null && attr.toLowerCase() === value && next(elem); }; } return function equals(elem){ return adapter.getAttributeValue(elem, name) === value && next(elem); }; } function _hyphen(next, data){ var name = data.name, value = data.value, len = value.length; if(data.ignoreCase){ value = value.toLowerCase(); return function hyphenIC(elem){ var attr = adapter.getAttributeValue(elem, name); return attr != null && (attr.length === len || attr.charAt(len) === "-") && attr.substr(0, len).toLowerCase() === value && next(elem); }; } return function (elem){ var attr = adapter.getAttributeValue(elem, name); return attr != null && attr.substr(0, len) === value && (attr.length === len || attr.charAt(len) === "-") && next(elem); }; } function _element(next, data){ var name = data.name, value = data.value; if (data.name === 'class') { let value = data.value; if (/\s/.test(value)) return function() { return false } return function(elem) { let classes = elem.classes; return classes && classes.has(value) && next(elem) } } else { if(/\s/.test(value)){ return falseFunc; } value = value.replace(reChars, "\\$&"); var pattern = "(?:^|\\s)" + value + "(?:$|\\s)", flags = data.ignoreCase ? "i" : "", regex = new RegExp(pattern, flags); return function(elem){ var attr = adapter.getAttributeValue(elem, name); return attr != null && regex.test(attr) && next(elem); } } } function _exists(next, data){ var name = data.name; return function(elem){ return adapter.hasAttrib(elem, name) && next(elem); }; } function _start(next, data){ var name = data.name, value = data.value, len = value.length; if(len === 0){ return falseFunc; } if(data.ignoreCase){ value = value.toLowerCase(); return function (elem){ var attr = adapter.getAttributeValue(elem, name); return attr != null && attr.substr(0, len).toLowerCase() === value && next(elem); }; } return function (elem){ var attr = adapter.getAttributeValue(elem, name); return attr != null && attr.substr(0, len) === value && next(elem); } } function _end(next, data){ var name = data.name, value = data.value, len = -value.length; if(len === 0){ return falseFunc; } if(data.ignoreCase){ value = value.toLowerCase(); return function endIC(elem){ var attr = adapter.getAttributeValue(elem, name); return attr != null && attr.substr(len).toLowerCase() === value && next(elem); }; } return function (elem){ var attr = adapter.getAttributeValue(elem, name); return attr != null && attr.substr(len) === value && next(elem); }; } function _any(next, data){ var name = data.name, value = data.value; if(value === ""){ return falseFunc; } if(data.ignoreCase){ var regex = new RegExp(value.replace(reChars, "\\$&"), "i"); return function (elem){ var attr = adapter.getAttributeValue(elem, name); return attr != null && regex.test(attr) && next(elem); }; } return function (elem){ var attr = adapter.getAttributeValue(elem, name); return attr != null && attr.indexOf(value) >= 0 && next(elem); }; } function _not(next, data){ var name = data.name, value = data.value; if(value === ""){ return function notEmpty(elem){ return !!adapter.getAttributeValue(elem, name) && next(elem); }; } else if(data.ignoreCase){ value = value.toLowerCase(); return function notIC(elem){ var attr = adapter.getAttributeValue(elem, name); return attr != null && attr.toLowerCase() !== value && next(elem); }; } return function not(elem){ return adapter.getAttributeValue(elem, name) !== value && next(elem); }; } let attributeRules = new Map([ ['equals', _equals], ['hyphen', _hyphen], ['element', _element], ['exists', _exists], ['start', _start], ['end', _end], ['any', _any], ['not', _not] ]); return { compile: function(next, data, options){ if(options && options.strict && ( data.ignoreCase || data.action === "not" )) throw new Error("Unsupported attribute selector"); return attributeRules.get(data.action)(next, data); }, rules: attributeRules }; } var attributes$1 = factory; function generalFactory(adapter, Pseudos){ //tags function _tag(next, data){ var name = data.name; return function tag(elem){ return adapter.getNameWithoutNS(elem) === name && next(elem); } } //traversal function _descendant(next){ return function descendant(elem){ var found = false; while(!found && (elem = adapter.getParent(elem))){ found = next(elem); } return found; }; } function __flexibleDescendant(next){ // Include element itself, only used while querying an array return function descendant(elem){ var found = next(elem); while(!found && (elem = adapter.getParent(elem))){ found = next(elem); } return found; }; } function _parent(next, data, options){ if(options && options.strict) throw new Error("Parent selector isn't part of CSS3"); return function parent(elem){ return adapter.getChildren(elem).some(test); }; function test(elem){ return adapter.isTag(elem) && next(elem); } } function _child(next){ return function child(elem){ var parent = adapter.getParent(elem); return !!parent && next(parent); }; } function _sibling(next){ return function sibling(elem){ var siblings = adapter.getSiblings(elem); for(var i = 0; i < siblings.length; i++){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) break; if(next(siblings[i])) return true; } } return false; }; } function _adjacent(next){ return function adjacent(elem){ var siblings = adapter.getSiblings(elem), lastElement; for(var i = 0; i < siblings.length; i++){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) break; lastElement = siblings[i]; } } return !!lastElement && next(lastElement); }; } function _universal(next){ return next; } const generalRules = new Map([ ['attribute', attributes$1(adapter).compile], ['pseudo', Pseudos.compile], ['tag', _tag], ['descendant', _descendant], ['_flexibleDescendant', __flexibleDescendant], ['parent', _parent], ['child', _child], ['sibling', _sibling], ['adjacent', _adjacent], ['universal', _universal], ]); return generalRules } var general = generalFactory; /* pseudo selectors --- they are available in two forms: * filters called when the selector is compiled and return a function that needs to return next() * pseudos get called on execution they need to return a boolean */ var trueFunc = boolbase.trueFunc, falseFunc$1 = boolbase.falseFunc; function filtersFactory(adapter){ var attributes = attributes$1(adapter), checkAttrib = attributes.rules.get('equals'); //helper methods function equals(a, b){ if(typeof adapter.equals === "function") return adapter.equals(a, b); return a === b; } function getAttribFunc(name, value){ var data = {name: name, value: value}; return function attribFunc(next){ return checkAttrib(next, data); }; } function getChildFunc(next){ return function(elem){ return !!adapter.getParent(elem) && next(elem); }; } function _contains (next, text){ return function (elem){ return next(elem) && adapter.getText(elem).indexOf(text) >= 0; } } function _icontains (next, text){ var itext = text.toLowerCase(); return function (elem){ return next(elem) && adapter.getText(elem).toLowerCase().indexOf(itext) >= 0; } } function _nthChild (next, rule){ var func = nthCheck(rule); if(func === falseFunc$1) return func; if(func === trueFunc) return getChildFunc(next); return function (elem) { var siblings = adapter.getSiblings(elem); for(var i = 0, pos = 0; i < siblings.length; i++){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) break; else pos++; } } return func(pos) && next(elem); } } function _nthLastChild (next, rule){ var func = nthCheck(rule); if(func === falseFunc$1) return func; if(func === trueFunc) return getChildFunc(next); return function (elem){ var siblings = adapter.getSiblings(elem); for(var pos = 0, i = siblings.length - 1; i >= 0; i--){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) break; else pos++; } } return func(pos) && next(elem); } } function _nthOfType (next, rule){ var func = nthCheck(rule); if(func === falseFunc$1) return func; if(func === trueFunc) return getChildFunc(next); return function (elem) { var siblings = adapter.getSiblings(elem); for(var pos = 0, i = 0; i < siblings.length; i++){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) break; if(adapter.getName(siblings[i]) === adapter.getName(elem)) pos++; } } return func(pos) && next(elem); } } function _nthLastOfType (next, rule){ var func = nthCheck(rule); if(func === falseFunc$1) return func; if(func === trueFunc) return getChildFunc(next); return function nthLastOfType(elem){ var siblings = adapter.getSiblings(elem); for(var pos = 0, i = siblings.length - 1; i >= 0; i--){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) break; if(adapter.getName(siblings[i]) === adapter.getName(elem)) pos++; } } return func(pos) && next(elem); } } function _root (next){ return function (elem){ return !adapter.getParent(elem) && next(elem); } } function _scope (next, rule, options, context){ if(!context || context.length === 0){ //equivalent to :root return filters.get('root')(next); } if(context.length === 1){ //NOTE: can't be unpacked, as :has uses this for side-effects return function (elem){ return equals(context[0], elem) && next(elem); } } return function (elem){ return context.indexOf(elem) >= 0 && next(elem); }; } const filters = new Map([ ['contains', _contains], ['icontains', _icontains], ['nth-child', _nthChild], ['nth-last-child', _nthLastChild], ['nth-of-type', _nthOfType], ['nth-last-of-type', _nthLastOfType], ['root', _root], ['scope', _scope], ['checkbox', getAttribFunc("type", "checkbox")], ['file', getAttribFunc("type", "file")], ['password', getAttribFunc("type", "password")], ['radio', getAttribFunc("type", "radio")], ['reset', getAttribFunc("type", "reset")], ['image', getAttribFunc("type", "image")], ['submit', getAttribFunc("type", "submit")] ]); return filters } function pseudosFactory(adapter){ //helper methods function getFirstElement(elems){ for(var i = 0; elems && i < elems.length; i++){ if(adapter.isTag(elems[i])) return elems[i]; } } function _empty(elem){ return !adapter.getChildren(elem).some(function(elem){ return adapter.isTag(elem) || elem.type === "text"; }) } function _firstChild(elem){ return getFirstElement(adapter.getSiblings(elem)) === elem; } function _lastChild(elem){ var siblings = adapter.getSiblings(elem); for(var i = siblings.length - 1; i >= 0; i--){ if(siblings[i] === elem) return true; if(adapter.isTag(siblings[i])) break; } return false; } function _firstOfType (elem){ var siblings = adapter.getSiblings(elem); for(var i = 0; i < siblings.length; i++){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) return true; if(adapter.getName(siblings[i]) === adapter.getName(elem)) break; } } return false; } function _lastOfType (elem){ var siblings = adapter.getSiblings(elem); for(var i = siblings.length - 1; i >= 0; i--){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) return true; if(adapter.getName(siblings[i]) === adapter.getName(elem)) break; } } return false; } function _onlyOfType (elem){ var siblings = adapter.getSiblings(elem); for(var i = 0, j = siblings.length; i < j; i++){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) continue; if(adapter.getName(siblings[i]) === adapter.getName(elem)) return false; } } return true; } function _onlyChild (elem){ var siblings = adapter.getSiblings(elem); for(var i = 0; i < siblings.length; i++){ if(adapter.isTag(siblings[i]) && siblings[i] !== elem) return false; } return true; } //:matches(a, area, link)[href] function _link (elem){ return adapter.hasAttrib(elem, "href"); } //:matches([selected], select:not([multiple]):not(> option[selected]) > option:first-of-type) function _selected (elem){ if(adapter.hasAttrib(elem, "selected")) return true; else if(adapter.getName(elem) !== "option") return false; //the first <option> in a <select> is also selected var parent = adapter.getParent(elem); if( !parent || adapter.getName(parent) !== "select" || adapter.hasAttrib(parent, "multiple") ) return false; var siblings = adapter.getChildren(parent), sawElem = false; for(var i = 0; i < siblings.length; i++){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem){ sawElem = true; } else if(!sawElem){ return false; } else if(adapter.hasAttrib(siblings[i], "selected")){ return false; } } } return sawElem; } //https://html.spec.whatwg.org/multipage/scripting.html#disabled-elements //:matches( // :matches(button, input, select, textarea, menuitem, optgroup, option)[disabled], // optgroup[disabled] > option), // fieldset[disabled] * //TODO not child of first <legend> //) function _disabled (elem){ return adapter.hasAttrib(elem, "disabled"); } function _enabled(elem){ return !adapter.hasAttrib(elem, "disabled"); } //:matches(input, select, textarea)[required] function _required(elem){ return adapter.hasAttrib(elem, "required"); } //:matches(input, select, textarea):not([required]) function _optional(elem){ return !adapter.hasAttrib(elem, "required"); } //:not(:empty) function _parent (elem){ return !pseudos.get('empty')(elem); } //:matches(h1, h2, h3, h4, h5, h6) function _header (elem){ var name = adapter.getName(elem); return name === "h1" || name === "h2" || name === "h3" || name === "h4" || name === "h5" || name === "h6"; } //:matches(button, input[type=button]) function _button(elem){ var name = adapter.getName(elem); return name === "button" || name === "input" && adapter.getAttributeValue(elem, "type") === "button"; } //:matches(input, textarea, select, button) function _input(elem){ var name = adapter.getName(elem); return name === "input" || name === "textarea" || name === "select" || name === "button"; } //input:matches(:not([type!='']), [type='text' i]) function _text(elem){ var attr; return adapter.getName(elem) === "input" && ( !(attr = adapter.getAttributeValue(elem, "type")) || attr.toLowerCase() === "text" ); } const pseudos = new Map([ ['empty', _empty], ['first-child', _firstChild], ['last-child', _lastChild], ['first-of-type', _firstOfType], ['last-of-type', _lastOfType], ['only-of-type', _onlyOfType], ['only-child', _onlyChild], ['link', _link], ['visited', falseFunc$1], ['selected', _selected], ['disabled', _disabled], ['enabled', _enabled], ['required', _required], ['optional', _optional], ['parent', _parent], ['header', _header], ['button', _button], ['input', _input], ['text', _text] ]); return pseudos; } function verifyArgs(func, name, subselect){ if(subselect === null){ if(func.length > 1 && name !== "scope"){ throw new Error("pseudo-selector :" + name + " requires an argument"); } } else { if(func.length === 1){ throw new Error("pseudo-selector :" + name + " doesn't have any arguments"); } } } //FIXME this feels hacky var re_CSS3 = /^(?:(?:nth|last|first|only)-(?:child|of-type)|root|empty|(?:en|dis)abled|checked|not)$/; function factory$1(adapter){ var pseudos = pseudosFactory(adapter); var filters = filtersFactory(adapter); return { compile: function(next, data, options, context){ var name = data.name, subselect = data.data; if(options && options.strict && !re_CSS3.test(name)){ throw new Error(":" + name + " isn't part of CSS3"); } let filter = filters.get(name); let pseudo = pseudos.get(name); if(typeof filter === "function"){ verifyArgs(filter, name, subselect); return filter(next, subselect, options, context); } else if(typeof pseudo === "function"){ verifyArgs(pseudo, name, subselect); if(next === trueFunc) return pseudo; return function pseudoArgs(elem){ return pseudo(elem, subselect) && next(elem); }; } else { throw new Error("unmatched pseudo-class :" + name); } }, filters, pseudos }; } var pseudos = factory$1; /* compiles a selector to an executable function */ var compile = compileFactory; var trueFunc$1 = boolbase.trueFunc, falseFunc$2 = boolbase.falseFunc; function compileFactory(adapter){ var Pseudos = pseudos(adapter), filters = Pseudos.filters, Rules = general(adapter, Pseudos); function compile(selector, options, context){ var next = compileUnsafe(selector, options, context); return wrap(next); } function wrap(next){ return function base(elem){ return adapter.isTag(elem) && next(elem); }; } function compileUnsafe(selector, options, context){ var token = cssWhat(selector, options); return compileToken(token, options, context); } function includesScopePseudo(t){ return t.type === "pseudo" && ( t.name === "scope" || ( Array.isArray(t.data) && t.data.some(function(data){ return data.some(includesScopePseudo); }) ) ); } var DESCENDANT_TOKEN = {type: "descendant"}, FLEXIBLE_DESCENDANT_TOKEN = {type: "_flexibleDescendant"}, SCOPE_TOKEN = {type: "pseudo", name: "scope"}, PLACEHOLDER_ELEMENT = {}; //CSS 4 Spec (Draft): 3.3.1. Absolutizing a Scope-relative Selector //http://www.w3.org/TR/selectors4/#absolutizing function absolutize(token, context){ //TODO better check if context is document var hasContext = !!context && !!context.length && context.every(function(e){ return e === PLACEHOLDER_ELEMENT || !!adapter.getParent(e); }); token.forEach(function(t){ if(t.length > 0 && isTraversal(t[0]) && t[0].type !== "descendant"); else if(hasContext && !includesScopePseudo(t)){ t.unshift(DESCENDANT_TOKEN); } else { return; } t.unshift(SCOPE_TOKEN); }); } function compileToken(token, options, context){ token = token.filter(function(t){ return t.length > 0; }); token.forEach(sort); var isArrayContext = Array.isArray(context); context = (options && options.context) || context; if(context && !isArrayContext) context = [context]; absolutize(token, context); var shouldTestNextSiblings = false; var query = token .map(function(rules){ if(rules[0] && rules[1] && rules[0].name === "scope"){ var ruleType = rules[1].type; if(isArrayContext && ruleType === "descendant") rules[1] = FLEXIBLE_DESCENDANT_TOKEN; else if(ruleType === "adjacent" || ruleType === "sibling") shouldTestNextSiblings = true; } return compileRules(rules, options, context); }) .reduce(reduceRules, falseFunc$2); query.shouldTestNextSiblings = shouldTestNextSiblings; return query; } function isTraversal(t){ return procedure.get(t.type) < 0; } function compileRules(rules, options, context){ return rules.reduce(function(func, rule){ if(func === falseFunc$2) return func; return Rules.get(rule.type)(func, rule, options, context); }, options && options.rootFunc || trueFunc$1); } function reduceRules(a, b){ if(b === falseFunc$2 || a === trueFunc$1){ return a; } if(a === falseFunc$2 || b === trueFunc$1){ return b; } return function combine(elem){ return a(elem) || b(elem); }; } function containsTraversal(t){ return t.some(isTraversal); } //:not, :has and :matches have to compile selectors //doing this in lib/pseudos.js would lead to circular dependencies, //so we add them here filters.set('not', function(next, token, options, context){ var opts = { xmlMode: !!(options && options.xmlMode), strict: !!(options && options.strict) }; if(opts.strict){ if(token.length > 1 || token.some(containsTraversal)){ throw new Error("complex selectors in :not aren't allowed in strict mode"); } } var func = compileToken(token, opts, context); if(func === falseFunc$2) return next; if(func === trueFunc$1) return falseFunc$2; return function(elem){ return !func(elem) && next(elem); }; }); filters.set('has', function(next, token, options){ var opts = { xmlMode: !!(options && options.xmlMode), strict: !!(options && options.strict) }; //FIXME: Uses an array as a pointer to the current element (side effects) var context = token.some(containsTraversal) ? [PLACEHOLDER_ELEMENT] : null; var func = compileToken(token, opts, context); if(func === falseFunc$2) return falseFunc$2; if(func === trueFunc$1){ return function(elem){ return adapter.getChildren(elem).some(adapter.isTag) && next(elem); }; } func = wrap(func); if(context){ return function has(elem){ return next(elem) && ( (context[0] = elem), adapter.existsOne(func, adapter.getChildren(elem)) ); }; } return function has(elem){ return next(elem) && adapter.existsOne(func, adapter.getChildren(elem)); }; }); filters.set('matches', function(next, token, options, context){ var opts = { xmlMode: !!(options && options.xmlMode), strict: !!(options && options.strict), rootFunc: next }; return compileToken(token, opts, context); }); compile.compileToken = compileToken; compile.compileUnsafe = compileUnsafe; compile.Pseudos = Pseudos; return compile; } var cssSelect = CSSselect; var falseFunc$3 = boolbase.falseFunc, defaultCompile = compile(domutils); function adapterCompile(adapter){ if(!adapter.__compile__){ adapter.__compile__ = compile(adapter); } return adapter.__compile__ } function getSelectorFunc(searchFunc){ return function select(query, elems, options){ options = options || {}; options.adapter = options.adapter || domutils; var compile = adapterCompile(options.adapter); if(typeof query !== "function") query = compile.compileUnsafe(query, options, elems); if(query.shouldTestNextSiblings) elems = appendNextSiblings((options && options.context) || elems, options.adapter); if(!Array.isArray(elems)) elems = options.adapter.getChildren(elems); else elems = options.adapter.removeSubsets(elems); return searchFunc(query, elems, options); }; } function getNextSiblings(elem, adapter){ var siblings = adapter.getSiblings(elem); if(!Array.isArray(siblings)) return []; siblings = siblings.slice(0); while(siblings.shift() !== elem); return siblings; } function appendNextSiblings(elems, adapter){ // Order matters because jQuery seems to check the children before the siblings if(!Array.isArray(elems)) elems = [elems]; var newElems = elems.slice(0); for(var i = 0, len = elems.length; i < len; i++){ var nextSiblings = getNextSiblings(newElems[i], adapter); newElems.push.apply(newElems, nextSiblings); } return newElems; } var selectAll = getSelectorFunc(function selectAll(query, elems, options){ return (query === falseFunc$3 || !elems || elems.length === 0) ? [] : options.adapter.findAll(query, elems); }); var selectOne = getSelectorFunc(function selectOne(query, elems, options){ return (query === falseFunc$3 || !elems || elems.length === 0) ? null : options.adapter.findOne(query, elems); }); function is(elem, query, options){ options = options || {}; options.adapter = options.adapter || domutils; var compile = adapterCompile(options.adapter); return (typeof query === "function" ? query : compile(query, options))(elem); } /* the exported interface */ function CSSselect(query, elems, options){ return selectAll(query, elems, options); } CSSselect.compile = defaultCompile; CSSselect.filters = defaultCompile.Pseudos.filters; CSSselect.pseudos = defaultCompile.Pseudos.pseudos; CSSselect.selectAll = selectAll; CSSselect.selectOne = selectOne; CSSselect.is = is; //legacy methods (might be removed) CSSselect.parse = defaultCompile; CSSselect.iterate = selectAll; //hooks CSSselect._compileUnsafe = defaultCompile.compileUnsafe; CSSselect._compileToken = defaultCompile.compileToken; export default cssSelect;