Last active
April 28, 2020 13:19
-
-
Save jonathantneal/6333494ea6dcbd5c10f1128b4b3e1a58 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// original size: 11659 / gzipped size: 3401 | |
voicss = (function(A, F, O, S) { | |
/* Helpers | |
/* ====================================================================== */ | |
var arrp = A.prototype | |
var objectAssign = O.assign || function(target) { | |
for (var i = 0, source; (source = ++i in arguments && O(arguments[i])); ) for (var name in source) objectHasOwnProperty(source, name) && (target[name] = source[name]) | |
return target | |
} | |
var objectCreate = O.create | |
var objectDefineProperty = Object.defineProperty | |
var objectGetPrototype = O.getPrototypeOf | |
var objectHasOwnProperty = F.call.bind(O.hasOwnProperty) | |
function splice(own, start, count, items) { | |
return arrp.splice.bind(own, start, count).apply(null, items) | |
} | |
function asString() { | |
return arrp.map.call(arguments, function (value) { | |
return value == null ? '' : S(value) | |
}).join('') | |
} | |
function defineProp(object, name, bitmask, valueOrGetter, setter) { | |
var descriptor = { configurable: bitmask & 2, enumerable: bitmask & 1 } | |
if (bitmask & 4) descriptor.writable = true | |
if (bitmask & 8) { | |
descriptor.get = valueOrGetter | |
descriptor.set = setter | |
} else descriptor.value = valueOrGetter | |
return objectDefineProperty(object, name, descriptor) | |
} | |
function defineProps(object, props) { | |
for (var name in props) defineProp(object, name, props[name][0], props[name][1]) | |
return object | |
} | |
function createClass(name, Class, Super, proto) { | |
return ( | |
types[name] = ( | |
( | |
Class = F('a,s,f,t', 'return f=function ' + name + '(){return a.apply(t=this instanceof f?this:s(f.prototype),arguments),t}')(Class, objectCreate) | |
).prototype = defineProps( | |
objectCreate(Super.prototype), | |
objectAssign({}, proto, { constructor: [6, Class] }) | |
) | |
).constructor | |
) | |
} | |
function createArray() { | |
var array = [] | |
arrp.push.apply(array, arguments) | |
return defineProp(array, 'toString', 6, function () { return this.join('') }) | |
} | |
function getFilteredNodes(nodes, parent) { | |
return arrp.reduce.call(nodes, function (nodes, node) { | |
nodes.push.apply( | |
nodes, | |
node !== O(node) | |
? getFilteredNodes(parse(node).value, parent) | |
: node instanceof CSSNode | |
? setParent(node) || [node] | |
: [] | |
) | |
return nodes | |
}, []) | |
function setParent(node) { | |
if (parent) node.parent = parent | |
} | |
} | |
function defineNode(node, definers, props) { | |
for (var name in definers) defineProp(node, name, 7, definers[name]) | |
defineProp(node, 'parent', 6, null) | |
defineProp(node, 'source', 6, { start: null, end: null }) | |
return objectAssign(node, props) | |
} | |
function trimArray(array) { | |
while (array[0] instanceof CSSWhitespace) array.shift() | |
while (array[array.length - 1] instanceof CSSWhitespace) array.pop() | |
return array | |
} | |
/* Parser | |
/* ====================================================================== */ | |
function parse(css) { | |
css = S(css) | |
var cssRegExp = /\/\*((?:[^*]|\*[^\/])*)\*\/|([ \t\n\f\r]+)|"((?:[^"\\\n\f\r]|\\\r\n|\\[\W\w])*)("|\n|\f|\r|$)|'((?:[^'\\\n\f\r]|\\\r\n|\\[\W\w])*)('|\n|\f|\r|$)|#((?:[-\w]|[^\x00-\x7F]|\\[^\n\f\r])+)|([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[Ee]-?\d+)?)|(-?(?:[-A-Z_a-z]|[^\x00-\x7F]|\\[^\n\f\r])(?:[-\w]|[^\x00-\x7F]|\\[^\n\f\r])*)|([\uD800-\uDBFF][\uDC00-\uDFFF]|[\W\w])/g | |
var cssMatches | |
var root = CSSBlock({ source: { start: 0, end: null, input: css } }) | |
var parent = root | |
var last = {} | |
var lastIndex = 0 | |
var source | |
while ((cssMatches = cssRegExp.exec(css)) != null) { | |
source = { | |
start: lastIndex, | |
end: ( | |
lastIndex = cssRegExp.lastIndex | |
) | |
} | |
parser.apply(cssRegExp, cssMatches) | |
} | |
return root | |
function parser(delimiter, comment, whitespace, stringDoubleQuote, stringDoubleQuoteDelimiterEnd, stringSingleQuote, stringSingleQuoteDelimiterEnd, hashIdentifier, number, nameIdentifier) { | |
if (comment != null) { | |
append(CSSComment, { value: comment }) | |
} else if (whitespace != null) { | |
append(CSSWhitespace, { value: whitespace }) | |
} else if (stringDoubleQuote != null) { | |
append(CSSString, { value: stringDoubleQuote, delimiterStart: '"', delimiterEnd: stringDoubleQuoteDelimiterEnd }) | |
} else if (stringSingleQuote != null) { | |
append(CSSString, { value: stringSingleQuote, delimiterStart: "'", delimiterEnd: stringSingleQuoteDelimiterEnd }) | |
} else if (hashIdentifier != null) { | |
append(CSSHashIdentifier, { value: hashIdentifier }) | |
} else if (number != null) { | |
append(CSSNumber, { value: number, unit: '' }) | |
} else if (nameIdentifier != null) { | |
if (last instanceof CSSNumber) { | |
last.unit = nameIdentifier | |
} else if (last.value === '@') { | |
splice(parent.value, -1, 1, [ last = CSSAtIdentifier({ parent: parent, value: nameIdentifier }) ]) | |
} else { | |
append(CSSNameIdentifier, { value: nameIdentifier }) | |
} | |
} else { | |
if (delimiter === '%' && last instanceof CSSNumber) { | |
last.unit = delimiter | |
} else if (delimiter === '(' && last instanceof CSSNameIdentifier) { | |
splice(parent.value, -1, 1, [parent = last = CSSFunction({ name: last.value, delimiterStart: delimiter, parent: parent }) ]) | |
} else if (delimiter === '(' || delimiter === '[' || delimiter === '{') { | |
parent = append(CSSBlock, { delimiterStart: delimiter, parent: parent }) | |
} else if ((delimiter === ')' && parent.delimiterStart === '(') || (delimiter === ']' && parent.delimiterStart === '[') || (delimiter === '}' && parent.delimiterStart === '{')) { | |
parent.delimiterEnd = delimiter | |
parent = parent.parent | |
} else { | |
append(CSSDelimiter, { value: delimiter }) | |
} | |
} | |
} | |
function append(CSSClass, details) { | |
parent.value.push(last = CSSClass(details)) | |
return objectAssign(last, { | |
parent: parent, | |
source: source | |
}) | |
} | |
} | |
/* Descriptors | |
/* ====================================================================== */ | |
var valueDescriptor = { | |
clone: [6, function clone(props, isPlain) { | |
var source = [] | |
var clones = [] | |
return objectAssign(cloneValue(this), props) | |
function cloneValue(value) { | |
return value === O(value) ? cloneObject(value) : value | |
} | |
function cloneObject(object) { | |
var cloneIndex | |
var cloned | |
if (~(cloneIndex = clones.indexOf(object))) cloned = clones[cloneIndex] | |
else source.push(object) && clones.push(cloned = A.isArray(object) ? createArray() : isPlain ? { type: null } : objectCreate(objectGetPrototype(object))) | |
for (var name in object) { | |
if (isPlain || objectHasOwnProperty(object, name)) { | |
cloned[name] = cloneValue(object[name]) | |
} | |
} | |
return cloned | |
} | |
}], | |
after: [6, function after() { | |
var parent = this.parent | |
var value = parent && parent.value | |
var index = value && value.indexOf(this) | |
if (~index) splice(value, index, 0, getFilteredNodes(arguments, parent)) | |
return this | |
}], | |
appendTo: [6, function appendTo(parent) { | |
if (parent) parent.append(this) | |
return this | |
}], | |
assign: [6, function assign() { | |
return objectAssign.bind(null, this).apply(null, arguments) | |
}], | |
before: [6, function before() { | |
var parent = this.parent | |
var value = parent && parent.value | |
var index = value && value.indexOf(this) | |
if (~index) splice(value, index, 0, getFilteredNodes(arguments, parent)) | |
return this | |
}], | |
define: [6, function define() { | |
arrp.forEach.call(arguments, defineProps.bind(null, this)) | |
return this | |
}], | |
parent: [6, null], | |
prependTo: [6, function prependTo(parent) { | |
if (parent) parent.prepend(this) | |
return this | |
}], | |
replaceWith: [6, function replaceWith() { | |
if (this.parent) this.parent.replace.bind(this.parent, this).apply(null, arguments) | |
return this | |
}], | |
root: [10, function () { | |
var root = this | |
do { root = root.parent } while (root) | |
return root | |
}], | |
toJSON: [6, function toJSON() { | |
return this.clone({}, true) | |
}], | |
toString: [6, function toString() { | |
return asString(this.value) | |
}], | |
type: [11, function () { | |
return this.constructor.name | |
}], | |
valueOf: [6, function valueOf() { | |
return this.clone({}, true) | |
}], | |
walk: [6, function walk(cbBefore, cbAfter) { | |
walkNode(this) | |
return this | |
function walkNode(parent) { | |
return ( | |
!A.isArray(parent.value) || | |
parent.value.slice(0).every(function (child, index) { | |
return ( | |
typeof cbBefore !== 'function' || | |
cbBefore(child, index) !== false || | |
( | |
walkNode(child) && | |
( | |
typeof cbAfter !== 'function' || | |
cbAfter(child, index) !== false | |
) | |
) | |
) | |
}) | |
) | |
} | |
}] | |
} | |
var blockDescriptor = { | |
append: [6, function append() { | |
arrp.push.apply(this.value, getFilteredNodes(arguments, this)) | |
return this | |
}], | |
first: [10, function () { | |
return this.value[0] || null | |
}], | |
last: [10, function () { | |
return this.value[this.value.length - 1] || null | |
}], | |
prepend: [6, function prepend() { | |
arrp.unshift.apply(this.value, getFilteredNodes(arguments, this)) | |
return this | |
}], | |
remove: [6, function remove(removee) { | |
var index = this.value.indexOf(removee) | |
if (~index) { | |
removee.parent = null | |
splice(this.value, index, 1, []) | |
} | |
return this | |
}], | |
replace: [6, function replace(replacee) { | |
var index = this.value.indexOf(replacee) | |
if (~index) { | |
replacee.parent = null | |
splice(this.value, index, 1, getFilteredNodes(arguments, this)) | |
} | |
return this | |
}], | |
toString: [6, function toString() { | |
return asString(this.delimiterStart, this.value, this.delimiterEnd) | |
}] | |
} | |
/* Nodes | |
/* ====================================================================== */ | |
var types = {} | |
function cssValueConstructor(props) { | |
defineNode(this, { value: null }, props) | |
} | |
function cssValueStringConstructor(props) { | |
defineNode(this, { value: '' }, props) | |
} | |
function cssValueArrayDelimiteredConstructor(props) { | |
defineNode(this, { value: createArray(), delimiterStart: '', delimiterEnd: '' }, props) | |
} | |
var CSSNode = createClass('CSSNode', function CSSNode(props) { | |
defineNode(this, null, props) | |
}, O) | |
var CSSValue = createClass('CSSValue', cssValueConstructor, CSSNode, valueDescriptor) | |
var CSSComment = createClass('CSSComment', function CSSComment(props) { | |
defineNode(this, { value: null, delimiterStart: '/*', delimiterEnd: '*/' }, props) | |
}, CSSValue, { | |
toString: [6, function toString() { | |
return asString(this.delimiterStart, this.value, this.delimiterEnd) | |
}] | |
}) | |
var CSSWhitespace = createClass('CSSWhitespace', cssValueStringConstructor, CSSValue) | |
var CSSString = createClass('CSSString', function CSSString(props) { | |
defineNode(this, { value: '', delimiterStart: '', delimiterEnd: '' }, props) | |
}, CSSValue, { | |
toString: [6, function toString() { | |
return asString(this.delimiterStart, this.value, this.delimiterEnd) | |
}] | |
}) | |
var CSSAtIdentifier = createClass('CSSAtIdentifier', cssValueStringConstructor, CSSValue, { | |
toString: [6, function toString() { | |
return asString('@', this.value) | |
}] | |
}) | |
var CSSHashIdentifier = createClass('CSSHashIdentifier', cssValueStringConstructor, CSSValue, { | |
toString: [6, function toString() { | |
return asString('#', this.value) | |
}] | |
}) | |
var CSSNameIdentifier = createClass('CSSNameIdentifier', cssValueStringConstructor, CSSValue) | |
var CSSNumber = createClass('CSSNumber', function CSSNumber(props) { | |
defineNode(this, { value: '', unit: '' }, props) | |
}, CSSValue, { | |
toString: [6, function toString() { | |
return asString(this.value, this.unit) | |
}] | |
}) | |
var CSSDelimiter = createClass('CSSDelimiter', cssValueStringConstructor, CSSValue) | |
var CSSBlock = createClass('CSSBlock', cssValueArrayDelimiteredConstructor, CSSValue, blockDescriptor) | |
var CSSFunction = createClass('CSSFunction', function CSSFunction(props) { | |
defineNode(this, { name: '', value: createArray(), delimiterStart: '(', delimiterEnd: ')' }, props) | |
}, CSSBlock, { | |
toString: [6, function toString() { | |
return asString(this.name, this.delimiterStart, this.value, this.delimiterEnd) | |
}] | |
}) | |
/* Elements | |
/* ====================================================================== */ | |
var CSSElement = createClass('CSSElement', cssValueConstructor, CSSNode, valueDescriptor) | |
var CSSDeclaration = createClass('CSSDeclaration', function CSSDeclaration(props) { | |
defineNode(this, { name: createArray(), value: createArray(), delimiterEnd: '' }, props) | |
}, CSSElement, { | |
nameText: [10, function () { | |
return asString(this.name).trim() | |
}], | |
valueText: [10, function () { | |
return asString(this.value).trim() | |
}], | |
toString: [6, function toString() { | |
return asString(this.name, ':', this.value) | |
}] | |
}) | |
var CSSRule = createClass('CSSRule', function CSSRule(props) { | |
defineNode(this, { prelude: createArray(), value: createArray(), delimiterStart: '', delimiterEnd: '' }, props) | |
}, CSSElement, objectAssign({}, blockDescriptor, { | |
preludeText: [10, function () { | |
return asString(this.prelude).trim() | |
}], | |
declarations: [10, function () { | |
return this.value.filter(function (node) { | |
return node instanceof CSSDeclaration | |
}) | |
}], | |
rules: [10, function () { | |
return this.value.filter(function (node) { | |
return node instanceof CSSRule | |
}) | |
}], | |
toString: [6, function toString() { | |
return asString(this.prelude, this.delimiterStart, this.value, this.delimiterEnd) | |
}] | |
})) | |
var CSSAtRule = createClass('CSSAtRule', function CSSAtRule(props) { | |
defineNode(this, { name: createArray(), prelude: createArray(), value: createArray(), delimiterStart: '', delimiterEnd: '' }, props) | |
}, CSSRule, { | |
toString: [6, function toString() { | |
return asString(this.name, this.prelude, this.delimiterStart, this.value, this.delimiterEnd) | |
}] | |
}) | |
var CSSStyleRule = createClass('CSSStyleRule', function CSSStyleRule(props) { | |
defineNode(this, { prelude: createArray(), value: createArray(), delimiterStart: '', delimiterEnd: '' }, props) | |
}, CSSRule, { | |
toString: [6, function toString() { | |
return asString(this.name, this.prelude, this.delimiterStart, this.value, this.delimiterEnd) | |
}] | |
}) | |
/* Selectors | |
/* ========================================================================== */ | |
var CSSSelectorNode = createClass('CSSSelectorNode', cssValueConstructor, CSSElement) | |
var CSSSimpleSelector = createClass('CSSSimpleSelector', cssValueConstructor, CSSSelectorNode, { | |
valueText: [10, function () { | |
return asString(this.value).trim() | |
}] | |
}) | |
var CSSTypeSelector = createClass('CSSTypeSelector', cssValueStringConstructor, CSSSimpleSelector) | |
var CSSIdSelector = createClass('CSSIdSelector', cssValueStringConstructor, CSSSimpleSelector) | |
var CSSUniversalSelector = createClass('CSSUniversalSelector', cssValueStringConstructor, CSSSimpleSelector) | |
var CSSClassSelector = createClass('CSSClassSelector', cssValueStringConstructor, CSSSimpleSelector, { | |
toString: [6, function toString() { | |
return asString('.', this.value) | |
}] | |
}) | |
var CSSPseudoSelector = createClass('CSSPseudoSelector', function CSSPseudoSelector(props) { | |
defineNode(this, { name: '', value: createArray(), colons: '', delimiterStart: '', delimiterEnd: '' }, props) | |
}, CSSSimpleSelector, { | |
toString: [6, function toString() { | |
return asString(this.colons, this.name, this.delimiterStart, this.value, this.delimiterEnd) | |
}] | |
}) | |
var CSSAttributeSelector = createClass('CSSAttributeSelector', function CSSAttributeSelector(props) { | |
defineNode(this, { name: createArray(), matcher: createArray(), value: createArray(), modifier: createArray() }, props) | |
}, CSSSimpleSelector, { | |
toString: [6, function toString() { | |
return asString(this.name, this.matcher, this.value, this.modifier) | |
}] | |
}) | |
var CSSCombinator = createClass('CSSCombinator', cssValueStringConstructor, CSSSelectorNode) | |
var CSSSelector = createClass('CSSSelector', function CSSSelector(props) { | |
defineNode(this, { value: createArray() }, props) | |
}, CSSElement, objectAssign({ | |
valueText: [10, function () { | |
return asString(this.value) | |
}] | |
}, blockDescriptor)) | |
/* Consumers | |
/* ========================================================================== */ | |
function consumeNodes(css) { | |
return typeof css === 'string' ? parse(css).value : A.isArray(css) ? css : [css] | |
} | |
function consumeCommaSeparatedList(nodes) { | |
nodes = consumeNodes(nodes) | |
var arg = CSSBlock() | |
var args = createArray(arg) | |
var node | |
while (node = nodes.shift()) { | |
if (node.value === ',') trimArray(arg.value) && args.push(arg = CSSBlock()) | |
else arg.value.push(node) | |
} | |
trimArray(arg.value) | |
return args | |
} | |
function consumeSpaceSeparatedList(nodes) { | |
nodes = consumeNodes(nodes) | |
var args = createArray() | |
var node | |
while (node = nodes.shift()) { | |
if (node instanceof CSSWhitespace) continue | |
else args.push(node) | |
} | |
return args | |
} | |
// original size: 433 / gzipped size: 233 | |
function consumeDeclaration(nodes) { | |
nodes = consumeNodes(nodes) | |
var undo = createArray() | |
var declaration = CSSDeclaration() | |
var declarationName = declaration.name | |
var declarationValue = declaration.value | |
var node | |
// whitespace before declaration name | |
while ((node = nodes[0]) instanceof CSSComment || node instanceof CSSWhitespace) declarationName.push(nodes.shift()) | |
// declaration name | |
if (node instanceof CSSNameIdentifier) declarationName.push(nodes.shift()) | |
else return undo | |
// whitespace after declaration name | |
while ((node = nodes[0]) instanceof CSSComment || node instanceof CSSWhitespace) declarationName.push(nodes.shift()) | |
// colon | |
if (node instanceof CSSDelimiter && node.value === ':') nodes.shift() | |
else return undo | |
// declaration value until semicolon or end | |
while ((node = nodes.shift())) { | |
if (node.value === ';') { | |
declaration.delimiterEnd = node.value | |
break | |
} else declarationValue.push(node) | |
} | |
return [ declaration ] | |
} | |
function consumeDeclarations(nodes, consumers) { | |
nodes = consumeNodes(nodes) | |
var declarations = createArray() | |
var node | |
while (node = nodes[0]) { | |
if (node instanceof CSSNameIdentifier) arrp.push.apply(declarations, consumeDeclaration(nodes)) | |
if (node instanceof CSSAtIdentifier || node.value === '&') arrp.push.apply(declarations, consumeRule(nodes, consumers)) | |
else declarations.push(nodes.shift()) | |
} | |
return declarations | |
} | |
function consumeRule(nodes, consumers) { | |
nodes = consumeNodes(nodes) | |
consumers = objectAssign({ | |
CSSAtRule: consumeRules, | |
CSSStyleRule: consumeDeclarations, | |
'CSSAtRule@nest': consumeDeclarations, | |
'CSSAtRule@nest.prelude': consumeSelectors | |
}, consumers) | |
var preludeArray1 = createArray() | |
var preludeArray2 = createArray() | |
var node | |
while (node = nodes[0]) { | |
// consume comments and whitespace | |
if (node instanceof CSSComment || node instanceof CSSWhitespace) preludeArray1.push(nodes.shift()) | |
// consume an at rule | |
else if (node instanceof CSSAtIdentifier) { | |
preludeArray1.push(nodes.shift()) | |
while (node = nodes[0]) { | |
// consume at at rule ending with a semicolon | |
if (node instanceof CSSBlock && node.delimiterStart === '{') return prepare( | |
CSSAtRule( | |
objectAssign({ name: preludeArray1, prelude: preludeArray2 }, nodes.shift()) | |
) | |
) | |
// consume at at rule ending with a block | |
if (node instanceof CSSDelimiter && node.value === ';') return nodes.shift(), prepare( | |
CSSAtRule({ name: preludeArray1, prelude: preludeArray2, delimiterEnd: ';' }) | |
) | |
// consume anything else | |
preludeArray2.push(nodes.shift()) | |
} | |
// return everything consumed because this is no rule | |
arrp.push.apply(preludeArray1, preludeArray2) | |
break | |
} else { | |
// consume an style rule | |
while (node = nodes[0]) { | |
if (node instanceof CSSBlock && node.delimiterStart === '{') return prepare( | |
CSSStyleRule( | |
objectAssign({ prelude: consumeSelectors(preludeArray1) }, nodes.shift()) | |
) | |
) | |
preludeArray1.push(nodes.shift()) | |
} | |
// return everything consumed because this is no rule | |
break | |
} | |
} | |
// return everything consumed because this is no rule | |
return preludeArray1 | |
function prepare(rule) { | |
var ruleType = rule.type | |
var ruleName = ruleType + asString(rule.name) | |
var ruleConsumer = consumers[ruleName] || consumers[ruleType] | |
var preludeConsumerType = asString(ruleType, '.prelude') | |
var preludeConsumerName = asString(ruleName, '.prelude') | |
var preludeConsumer = consumers[preludeConsumerType] || consumers[preludeConsumerName] | |
if (ruleType === 'CSSStyleRule') objectAssign(consumers, { | |
CSSAtRule: consumeDeclarations, | |
CSSStyleRule: consumeDeclarations | |
}) | |
if (typeof preludeConsumer === 'function') splice(rule.prelude, 0, 0, preludeConsumer(rule.prelude, consumers)) | |
if (typeof ruleConsumer === 'function') splice(rule.value, 0, 0, ruleConsumer(rule.value, consumers)) | |
return [ rule ] | |
} | |
} | |
function consumeRules(nodes, consumers) { | |
nodes = consumeNodes(nodes) | |
var rules = createArray() | |
var node | |
while (node = nodes[0]) { | |
if (node instanceof CSSComment || node instanceof CSSWhitespace || node instanceof CSSBlock) rules.push(nodes.shift()) | |
else arrp.push.apply(rules, consumeRule(nodes, consumers)) | |
} | |
return rules | |
} | |
function consumeSelector(nodes) { | |
nodes = consumeNodes(nodes) | |
var selector = createArray() | |
var selectorAfter = createArray() | |
var node | |
while ((node = nodes[0]) instanceof CSSComment || node instanceof CSSWhitespace) selector.push(nodes.shift()) | |
while ((node = nodes[nodes.length - 1]) instanceof CSSComment || node instanceof CSSWhitespace) selectorAfter.push(nodes.pop()) | |
while (node = nodes[0]) { | |
if (node instanceof CSSComment) selector.push(nodes.shift()) | |
else if (node instanceof CSSWhitespace) selector.push(consumeCombinator()) | |
else if (node instanceof CSSNameIdentifier) selector.push(CSSTypeSelector(nodes.shift())) | |
else if (node instanceof CSSHashIdentifier) selector.push(CSSIdSelector(nodes.shift())) | |
else if (node instanceof CSSDelimiter) { | |
if (node.value === ',') break | |
else if (node.value === '*') selector.push(CSSUniversalSelector(CSSUniversalSelector(nodes.shift()))) | |
else if (node.value === '.' && nodes[1] instanceof CSSNameIdentifier) selector.push(CSSClassSelector(nodes.shift() && nodes.shift())) | |
else if (node.value === ':') selector.push.apply(selector, consumePseudo()) | |
else if (node.value === '&') selector.push(CSSTypeSelector(nodes.shift())) | |
else selector.push(consumeCombinator()) | |
} else if (node instanceof CSSBlock) { | |
if (node.delimiterStart === '[') selector.push.apply(selector, consumeAttributeSelector(nodes.shift())) | |
else selector.push(prepare(CSSSelector(objectAssign({}, nodes.shift())))) | |
} | |
else selector.push(nodes.shift()) | |
} | |
selector.push.apply(selector, selectorAfter) | |
return [ CSSSelector({ value: selector }) ] | |
function consumeCombinator() { | |
var combinator = createArray() | |
while ((node = nodes[0]) instanceof CSSComment || node instanceof CSSWhitespace || node instanceof CSSDelimiter) combinator.push(nodes.shift()) | |
return CSSCombinator({ value: combinator }) | |
} | |
function consumePseudo() { | |
var undo = createArray() | |
var colons = '' | |
while ( | |
(node = nodes.shift()) instanceof CSSComment || | |
( | |
node instanceof CSSDelimiter && | |
node.value === ':' && | |
(colons += ':') | |
) | |
) undo.push(node) | |
return node instanceof CSSNameIdentifier | |
? prepare(CSSPseudoSelector({ name: node.value, colons: colons })) | |
: node instanceof CSSFunction | |
? prepare(CSSPseudoSelector(objectAssign({ colons: colons }, node))) | |
: nodes.unshift.apply(nodes, undo) && [consumeCombinator()] | |
} | |
function consumeAttributeSelector(block) { | |
var nodes = block.value | |
var attributeName = createArray() | |
var attributeMatcher = createArray() | |
var attributeValue = createArray() | |
var attributeModifier = createArray() | |
var node | |
// consume attribute name | |
while ((node = nodes.shift()) instanceof CSSComment || node instanceof CSSWhitespace) attributeName.push(node) | |
if (node instanceof CSSNameIdentifier) attributeName.push(node) | |
else return attributeName | |
while ((node = nodes.shift()) instanceof CSSComment || node instanceof CSSWhitespace) attributeName.push(node) | |
// return [{name}] | |
if (!nodes.length) return prepareAttribute() | |
// consume attribute matcher | |
if (node instanceof CSSDelimiter) while (node instanceof CSSComment || node instanceof CSSDelimiter) attributeMatcher.push(node) | |
else return attributeName.push.apply(attributeName, attributeMatcher), attributeName | |
while ((node = nodes.shift()) instanceof CSSComment || node instanceof CSSWhitespace) attributeMatcher.push(node) | |
// consume attribute value | |
if (node instanceof CSSString || node instanceof CSSNameIdentifier) attributeValue.push(node) | |
else return attributeName.push.apply(attributeName, attributeMatcher), attributeName | |
// consume attribute modifier | |
attributeModifier.push.apply(attributeModifier, nodes.splice(0)) | |
// return [{name}={value}{modifier}] | |
return prepareAttribute() | |
function prepareAttribute() { | |
return prepare(CSSAttributeSelector(objectAssign({}, block, { name: attributeName, matcher: attributeMatcher, value: attributeValue, modifier: attributeModifier }))) | |
} | |
} | |
function prepare(node) { | |
var name = asString(node.name) | |
if (!name || !/^nth-/.test(name)) splice(node.value, 0, 0, consumeSelectors(node.value)) | |
return [ node ] | |
} | |
} | |
function consumeSelectors(nodes) { | |
nodes = consumeNodes(nodes) | |
var selectors = [] | |
var selector = createArray() | |
var node | |
while (node = nodes[0]) { | |
if (node instanceof CSSDelimiter && node.value === ',') { | |
nodes.shift() | |
if (selector.length) { | |
selectors.push.apply(selectors, consumeSelector(selector)) | |
selector = createArray() | |
} | |
} else selector.push(nodes.shift()) | |
} | |
selectors.push.apply(selectors, consumeSelector(selector)) | |
return selectors | |
} | |
function consume(nodes, consumers) { | |
return CSSBlock({ value: consumeRules(nodes, consumers) }) | |
} | |
consume.commaSeparatedList = consumeCommaSeparatedList | |
consume.declaration = consumeDeclaration | |
consume.declarations = consumeDeclarations | |
consume.rule = consumeRule | |
consume.rules = consumeRules | |
consume.selector = consumeSelector | |
consume.selectors = consumeSelectors | |
consume.spaceSeparatedList = consumeSpaceSeparatedList | |
/* Library | |
/* ====================================================================== */ | |
return { | |
asString: asString, | |
consume: consume, | |
createClass: createClass, | |
defineProp: defineProp, | |
defineProps: defineProps, | |
objectAssign: objectAssign, | |
parse: parse, | |
types: types | |
} | |
})(Array, Function, Object, String) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment