UNPKG

zaftig

Version:
393 lines (350 loc) 14 kB
;(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 })