zaftig
Version:
css for your js
393 lines (350 loc) • 14 kB
JavaScript
;(function(root, factory) {
if(typeof define === 'function' && define.amd) { define([], factory) }
else if(typeof module === 'object' && module.exports) { module.exports = factory() }
else { root.z = factory() }
})(typeof self !== 'undefined' ? self : this, function() {
'use strict'
var exports = {}
var isArray = Array.isArray;
var hasOwnProperty = Object.hasOwnProperty;
var getPrototypeOf = Object.getPrototypeOf;
// const p = (...args) => (console.log(...args), args[0])
var err = function () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
return console.error.apply(console, [ 'zaftig:' ].concat( args ));
}
var zip = function (parts, args) { return parts.reduce(function (acc, c, i) { return acc + c + (args[i] == null ? '' : String(args[i])); }, ''); }
var memo = function (fn, cache) {
if ( cache === void 0 ) cache = {};
return function (x) { return (x in cache ? cache[x] : (cache[x] = fn(x))); };
}
var dash = function (nodash) { return nodash.replace(/[A-Z]/g, function (m) { return '-' + m.toLowerCase(); }); }
var initials = function (str) { return str[0] + str.slice(1).replace(/[a-z]/g, '').toLowerCase(); }
// determine vendor prefix for current browser
var vendorPrefix =
document.documentMode || /Edge\//.test(navigator.userAgent)
? 'ms'
: navigator.vendor
? 'webkit'
: 'moz'
// list of popular properties that should have a higher shorthand priority
var popular = [
'backgroundColor',
'borderBottom',
'borderRadius',
'bottom',
'boxShadow',
'color',
'display',
'flexDirection',
'float',
'fontFamily',
'fontSize',
'height',
'margin',
'marginTop',
'marginBottom',
'opacity',
'padding',
'paddingBottom',
'right',
'textAlign',
'textDecoration',
'top',
'whiteSpace',
'width'
]
// recursively search for style prototype used to establish valid props
var findStyleProto = function (obj) { return hasOwnProperty.call(obj, 'width') ? obj : findStyleProto(getPrototypeOf(obj)); }
// collect valid props and their shorthands
var props = Object.keys(findStyleProto(document.documentElement.style)).filter(
function (prop) { return prop.indexOf('-') < 0 && prop != 'length'; }
)
var validProps = {}
var short = {}
// concat valid popular props to give higher shorthand priority
props.concat(popular.filter(function (pop) { return props.indexOf(pop) >= 0; })).forEach(function (prop) {
var dashed = dash(prop)
var init = initials(prop)
if (prop.toLowerCase().indexOf(vendorPrefix) == 0) {
init = init.slice(1)
dashed = dashed[0] == '-' ? dashed : '-' + dashed
if (!short[init]) { short[init] = dashed }
} else { short[init] = dashed }
validProps[dashed] = true
})
// determines if given css prop takes pixels
var testDiv = document.createElement('div')
var needsPx = memo(
function (prop) { return ['0', '0 0'].some(function (val) {
testDiv.style.cssText = prop + ": " + val + ";"
return testDiv.style.cssText.slice(-3) == 'px;'
}); },
{
flex: false,
border: true,
'border-left': true,
'border-right': true,
'border-top': true,
'border-bottom': true
}
)
var selSep = /\s*,\s*/
// generates selector and combinations of selector and parent
var processSelector = function (sel, parentSel) { return parentSel
.split(selSep)
.reduce(
function (acc, parentPart) { return acc.concat(
sel
.split(selSep)
.map(function (part) { return part.indexOf('&') >= 0
? part.replace(/&/g, parentPart)
: parentPart + (part[0] == ':' || part[0] == '[' ? '' : ' ') + part; }
)
); },
[]
)
.join(',\n'); }
var wrap = function (sel, body) { return (sel && body ? ("\n" + sel + " {\n" + body + "}\n") : ''); }
var handleTemplate = function (fn) {
return function (parts) {
var args = [], len = arguments.length - 1;
while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
try {
return isArray(parts) ? fn.call(this, zip(parts, args)) : fn.call(this, parts)
} catch (e) {
err('error `', parts, '`', args, '\n', e)
return ''
}
}
}
var createSheet = function () { return document.head.appendChild(document.createElement('style')); }
var _testSheet
var isValidCss = function (sel, body) {
if ( body === void 0 ) body = '';
try {
// if sheet has been removed from DOM recreate it
if (!_testSheet || !_testSheet.sheet) { _testSheet = createSheet() }
_testSheet.sheet.insertRule((sel + "{" + body + "}"), 0)
var out = body && _testSheet.sheet.cssRules[0].cssText.replace(/\s/g, '')
_testSheet.sheet.deleteRule(0)
return !out || out.length > sel.length + 2
} catch (e) {
return false
}
}
var prefixSelector = function (sel) { return sel.replace(/(::?)([a-z-]+)(\()?/gi, function (full, pre, name, paran) {
// handle special browser cases
if (name == 'placeholder' && vendorPrefix != 'moz') { name = 'input-' + name }
else if (name == 'matches') { name = 'any' }
// skip valid or already prefixed selectors
return name[0] == '-' || isValidCss(paran ? full + '.f)' : full)
? full
: (pre + "-" + vendorPrefix + "-" + name + (paran || ''))
}); }
var makeZ = function (conf) {
if ( conf === void 0 ) conf = {};
var helpers = conf.helpers; if ( helpers === void 0 ) helpers = {};
var unit = conf.unit; if ( unit === void 0 ) unit = 'px';
var id = conf.id; if ( id === void 0 ) id = 'z' + Math.random().toString(36).slice(2);
var style = conf.style;
var dot = conf.dot; if ( dot === void 0 ) dot = true;
var debug = conf.debug; if ( debug === void 0 ) debug = false;
var idCount = 0
var Style = function Style(className) {
this.class = className
this.className = className
};
Style.prototype.toString = function toString () {
return this.class
};
Style.prototype.valueOf = function valueOf () {
return dot ? '.' + this.class : this.class
};
Style.prototype.z = function z$1 () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
return this.concat(z.apply(void 0, args))
};
Style.prototype.concat = function concat$1 () {
var things = [], len = arguments.length;
while ( len-- ) things[ len ] = arguments[ len ];
return concat.apply(void 0, [ this.class ].concat( things ))
};
var concat = function () {
var things = [], len = arguments.length;
while ( len-- ) things[ len ] = arguments[ len ];
var classes = []
things.forEach(function (thing) {
if (!thing) { return }
if (typeof thing === 'string') { classes.push(thing) }
else if (thing.className) { classes.push(thing.className) }
})
return new Style(classes.join(' '))
}
var addToSheet = function (sel, body, prefix) {
var rule = wrap(prefix ? prefixSelector(sel) : sel, body)
if (!rule) { return }
if (!style) {
style = createSheet()
style.id = id
}
try {
// run even in debug mode so that we can detect invalid syntax
style.sheet.insertRule(rule, style.sheet.cssRules.length)
// rule inserted above is overwritten when textContent is
if (debug) { style.textContent += rule }
} catch (e) {
// if insert fails, attempt again if selector can be prefixed
if (!prefix && sel.indexOf(':') >= 0) { addToSheet(sel, body, true) }
else { err('insert failed', sel, body, e) }
}
}
var isKeyframes = function (sel) { return sel && sel.indexOf('@keyframes') == 0; }
var indent = function (rules) { return rules.replace(/^/gm, ' ') + '\n'; }
var appendAtRule = function (sel, ctx, parentSel, parent) {
ctx._rules = wrap(parentSel == '' ? ':root' : parentSel, ctx._rules)
// for at-rules we have to run through nested blocks first to accumulate child _rules
ctx._nested.forEach(function (nested) { return appendRule(nested._selector, nested, parentSel, ctx); })
if (parent) { parent._rules += wrap(sel, indent(ctx._rules)) }
else { addToSheet(sel, indent(ctx._rules)) }
}
var appendRule = function (sel, ctx, parentSel, parent) {
if ( parentSel === void 0 ) parentSel = '';
if (!sel) { return debug && err('missing selector', ctx) }
if (/^@(media|keyframes|supports)/.test(sel)) { return appendAtRule(sel, ctx, parentSel, parent) }
// compute selector based on parentSel
if (parentSel && (!parent || !isKeyframes(parent._selector)))
{ sel = processSelector(sel, parentSel) }
// when we have a parent add rules there, otherwise directly to sheet
if (parent) { parent._rules += wrap(sel, ctx._rules) }
else { addToSheet(sel, ctx._rules) }
// don't include :root in nested rules
var nestingSel = sel == ':root' ? '' : sel
// process nested rules
ctx._nested.forEach(function (nestedCtx) { return appendRule(nestedCtx._selector, nestedCtx, nestingSel, parent); })
}
var runHelper = function (key, value) {
var helper = helpers[key]
return typeof helper === 'function'
? helper.apply(void 0, (value ? value.split(' ') : []))
: helper && helper + ' ' + value
}
var assignRule = function (ctx, prop, value) {
// if we only have a value then treat it as the prop
if (value && !prop) {
prop = value
value = ''
}
if (!prop) { return }
// special instructions
if (prop[0] == '$') {
if (prop == '$name') { return (ctx._name = value) }
if (prop == '$compose') { return (ctx._compose = value) }
// everything else is a css var
prop = '--' + prop.slice(1)
}
// handle helpers
var helper = runHelper(prop, value)
if (helper) {
var parsed = parseRules(helper)
ctx._rules += parsed._rules
ctx._nested = ctx._nested.concat(parsed._nested)
return
}
if (!value) { return debug && err('no value for', prop) }
// handle shorthands
prop = short[prop] || prop
// auto-prefix
if (!validProps[prop]) {
var prefixed = "-" + vendorPrefix + "-" + prop
if (validProps[prefixed]) { prop = prefixed }
}
// convert var refs
if (value.indexOf('$') >= 0) { value = value.replace(/\$([a-z0-9-]+)/gi, 'var(--$1)') }
// auto-px
if (needsPx(prop))
{ value = value
.split(' ')
.map(function (part) { return (isNaN(part) ? part : part + unit); })
.join(' ') }
var rule = " " + prop + ": " + value + ";\n"
if (debug && !isValidCss(id, rule)) { err('invalid css', rule) }
ctx._rules += rule
}
var PROP = 1
var VALUE = 2
var parseRules = memo(function (str) {
var ctx = [{ _rules: '', _nested: [] }]
str = str && str.trim()
if (!str) { return ctx[0] }
str += ';' // append semi to properly commit last rule
var mode = PROP
var buffer = ''
var depth = 0
var quote = ''
var curProp = ''
for (var i = 0; i < str.length; i++) {
var char = str[i]
if (char == '\n' || ((char == ';' || char == '}') && !quote)) {
// end of rule, process key/value
assignRule(ctx[depth], curProp, buffer.trim() + quote)
if (char == '}') { ctx[--depth]._nested.push(ctx.pop()) }
mode = PROP
curProp = buffer = quote = ''
} else if (char == '{' && !quote) {
// block open, pre-process helper and create new ctx
ctx[++depth] = {
_selector: runHelper(curProp, buffer.trim()) || (curProp + ' ' + buffer).trim(),
_rules: '',
_nested: []
}
mode = PROP
curProp = buffer = ''
} else if (mode == PROP) {
if (char == ' ') {
if ((curProp = buffer.trim())) {
// first space with value in curProp means we search for value
mode = VALUE
buffer = ''
}
} else { buffer += char }
} else if (mode == VALUE) {
// ignore special parser tokens while inside of quotes
if (quote) {
if (char == quote && str[i - 1] != '\\') { quote = '' }
} else if (char == "'" || char == '"') {
quote = char
}
buffer += char
}
}
return ctx[0]
})
var createKeyframes = memo(function (rules) {
var name = 'anim-' + id + '-' + (idCount += 1)
appendRule('@keyframes ' + name, parseRules(rules))
return name
})
var createStyle = memo(function (rules) {
var parsed = parseRules(rules)
var className = (parsed._name ? parsed._name + '-' : '') + id + '-' + (idCount += 1)
appendRule('.' + className, parsed)
return new Style(className + (parsed._compose ? ' ' + parsed._compose : ''))
})
var z = handleTemplate(createStyle)
z.anim = handleTemplate(createKeyframes)
z.concat = concat
z.getSheet = function () { return style; }
z.global = handleTemplate(function (rules) { return appendRule(':root', parseRules(rules)); })
z.helper = function (spec) { return Object.assign(helpers, spec); }
z.new = makeZ
z.setDebug = function (flag) { return (debug = flag); }
z.setDot = function (flag) { return (dot = flag); }
z.style = handleTemplate(function (rules) { return parseRules(rules)._rules; })
return z
}
exports = Object.assign(makeZ(), exports)
return exports
})