From 817862f5e9ea153021b14c172898eb55ba030abe Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Mon, 4 Dec 2017 15:17:50 -0800 Subject: [PATCH 1/3] added rules attribute for handling classes --- packages/heml-render/src/renderElement.js | 67 +++++++++++++++---- .../heml-render/src/stringifyAttributes.js | 9 +++ 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/packages/heml-render/src/renderElement.js b/packages/heml-render/src/renderElement.js index 1053ff0..8cf76ef 100644 --- a/packages/heml-render/src/renderElement.js +++ b/packages/heml-render/src/renderElement.js @@ -1,5 +1,5 @@ import isPromise from 'is-promise' -import { isPlainObject, defaults, mapValues, castArray, compact, flattenDeep } from 'lodash' +import { isPlainObject, isString, defaults, mapValues, castArray, compact, flattenDeep, map, keys, first } from 'lodash' import createHtmlElement from './createHtmlElement' export default function (name, attrs, ...contents) { @@ -15,15 +15,7 @@ function render (name, attrs, contents) { } if (isPlainObject(name) && name.render) { - /** set the defaults and massage attribute values */ - attrs = defaults({}, attrs, name.defaultAttrs || {}) - attrs = mapValues(attrs, (value, name) => { - if ((value === '' && name !== 'class') || value === 'true' || value === 'on') { return true } - - if (value === 'false' || value === 'off') { return false } - - return value - }) + const element = name /** * custom elements can return promises, arrays, or strings @@ -33,11 +25,11 @@ function render (name, attrs, contents) { * 2. return a string synchronously if we can * 3. return a string in a promise */ - const renderResults = castArray(name.render(attrs, contents)) + const renderResults = castArray(element.render(prepareElementAttributes(element, attrs), contents)) /** 1. catch shorthands for rerendering the element */ if (renderResults.length === 1 && renderResults[0] === true) { - return render(name.tagName, attrs, contents) + return render(element.tagName, attrs, contents) } /** 2. we want to return synchronously if we can */ @@ -53,7 +45,56 @@ function render (name, attrs, contents) { /** if we have a regular ol element go ahead and convert it to a string */ if (attrs && attrs.class === '') { delete attrs.class } - if (attrs && attrs.class) { attrs.class = attrs.class.trim() } + if (attrs && isString(attrs.class)) { attrs.class = attrs.class.trim() } return createHtmlElement({ name, attrs, contents }) } + + +function prepareElementAttributes(element, attrs) { + /** set the defaults and massage attribute values */ + attrs = defaults({}, attrs, element.defaultAttrs || {}) + attrs = mapValues(attrs, (value, name) => { + if ((value === '' && name !== 'class') || value === 'true' || value === 'on') { return true } + + if (value === 'false' || value === 'off') { return false } + + return value + }) + + /** set up the rules attribute to mirror the rules set on the element */ + if (element.rules) { + attrs.rules = {} + + const childClasses = buildChildClasses(attrs) + + for (let [ selector, decls ] of Object.entries(element.rules)) { + const pseudo = findPseudo(decls) + + attrs.rules[pseudo || selector] = { + className: [ ...compact(selector.split('.')), ...(pseudo === 'root' ? attrs.class.split(/\s+/) : childClasses) ] + } + } + + delete attrs.class + } + + return attrs +} + +function buildChildClasses({ id, class: classes }) { + classes = isString(classes) ? compact(classes.split(/\s+/)) : classes + + const childClasses = classes.map((c) => `c-${c}`) + + return !!id ? [ `i-${id}`, ...childClasses ] : childClasses +} + +function findPseudo(decls) { + for (let decl of decls) { + const firstKey = first(keys(decl)) + if (isPlainObject(decl) && firstKey === '@pseudo') { + return decl = decl[firstKey] + } + } +} diff --git a/packages/heml-render/src/stringifyAttributes.js b/packages/heml-render/src/stringifyAttributes.js index 1f1824b..576c5e7 100644 --- a/packages/heml-render/src/stringifyAttributes.js +++ b/packages/heml-render/src/stringifyAttributes.js @@ -2,11 +2,20 @@ export default function stringifyAttributes (attrsObj) { const attributes = [] + const className = Object.entries(attrsObj).find(([ key ]) => key === 'className') + delete attrsObj.className + attrsObj['class'] = attrsObj['class'] || '' + + for (let [ key, value ] of Object.entries(attrsObj)) { if (value === false) { continue } if (Array.isArray(value)) { value = value.join(' ') } + if (key === 'class' && className) { + value = `${value} ${Array.isArray(className[1]) ? className[1].join(' ') : className[1]}`.trim() + } + value = value === true ? '' : `="${String(value)}"` attributes.push(`${key}${value}`) From 2ccdeb8e41835f0975b0093e8da2900e5de98062 Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Mon, 4 Dec 2017 15:18:48 -0800 Subject: [PATCH 2/3] added scoping and similifed the API for creating element rules --- packages/heml-render/src/stringifyAttributes.js | 10 ++++++---- packages/heml-utils/package-lock.json | 9 ++++++++- packages/heml-utils/package.json | 3 ++- packages/heml-utils/src/createElement.js | 13 ++++++++++++- packages/heml-utils/src/hash.js | 5 +++++ 5 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 packages/heml-utils/src/hash.js diff --git a/packages/heml-render/src/stringifyAttributes.js b/packages/heml-render/src/stringifyAttributes.js index 576c5e7..703bc96 100644 --- a/packages/heml-render/src/stringifyAttributes.js +++ b/packages/heml-render/src/stringifyAttributes.js @@ -1,10 +1,12 @@ /** escapeless version of npmjs.com/stringify-attributes */ export default function stringifyAttributes (attrsObj) { const attributes = [] + const className = attrsObj['className'] - const className = Object.entries(attrsObj).find(([ key ]) => key === 'className') - delete attrsObj.className - attrsObj['class'] = attrsObj['class'] || '' + if (className) { + delete attrsObj.className + attrsObj['class'] = attrsObj['class'] || '' + } for (let [ key, value ] of Object.entries(attrsObj)) { @@ -13,7 +15,7 @@ export default function stringifyAttributes (attrsObj) { if (Array.isArray(value)) { value = value.join(' ') } if (key === 'class' && className) { - value = `${value} ${Array.isArray(className[1]) ? className[1].join(' ') : className[1]}`.trim() + value = `${value} ${Array.isArray(className) ? className.join(' ') : String(className)}`.trim() } value = value === true ? '' : `="${String(value)}"` diff --git a/packages/heml-utils/package-lock.json b/packages/heml-utils/package-lock.json index 4209c69..af06994 100644 --- a/packages/heml-utils/package-lock.json +++ b/packages/heml-utils/package-lock.json @@ -1,6 +1,8 @@ { - "requires": true, + "name": "@heml/utils", + "version": "1.1.2", "lockfileVersion": 1, + "requires": true, "dependencies": { "css-groups": { "version": "0.1.1", @@ -11,6 +13,11 @@ "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "shorthash": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/shorthash/-/shorthash-0.0.2.tgz", + "integrity": "sha1-WbJo7sveWQOLMNogK8+93rLEpOs=" } } } diff --git a/packages/heml-utils/package.json b/packages/heml-utils/package.json index 9e8a5c7..524cf2a 100644 --- a/packages/heml-utils/package.json +++ b/packages/heml-utils/package.json @@ -23,6 +23,7 @@ "dependencies": { "@heml/render": "^1.1.2", "css-groups": "^0.1.1", - "lodash": "^4.17.4" + "lodash": "^4.17.4", + "shorthash": "0.0.2" } } diff --git a/packages/heml-utils/src/createElement.js b/packages/heml-utils/src/createElement.js index e39b887..c4c0f2c 100644 --- a/packages/heml-utils/src/createElement.js +++ b/packages/heml-utils/src/createElement.js @@ -1,4 +1,5 @@ -import { defaults, isFunction } from 'lodash' +import { defaults, isFunction, mapKeys, mapValues } from 'lodash' +import hash from './hash' const textRegex = /^(text(-([^-\s]+))?(-([^-\s]+))?|word-(break|spacing|wrap)|line-break|hanging-punctuation|hyphens|letter-spacing|overflow-wrap|tab-size|white-space|font-family|font-weight|font-style|font-variant|color)$/i @@ -11,6 +12,16 @@ export default function (name, element) { element = { render: element } } + if (element.rules) { + element.rules = mapValues(element.rules, (decls, key) => { + return [ { '@pseudo': key }, ...decls ] + }) + + element.rules = mapKeys(element.rules, (decls, key) => { + return `.${hash(name)}__${key}` + }) + } + if (element.containsText) { element.rules = element.rules || {} element.rules['.header'] = [ textRegex ] diff --git a/packages/heml-utils/src/hash.js b/packages/heml-utils/src/hash.js new file mode 100644 index 0000000..5e6a4dd --- /dev/null +++ b/packages/heml-utils/src/hash.js @@ -0,0 +1,5 @@ +import { unique as shorthash } from 'shorthash' + +export default function hash(str, prefix = 'h') { + return `${prefix}${shorthash(str)}` +} From f9e7a5a50b72d986090dec72fde46ec24be41521 Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Mon, 4 Dec 2017 17:10:41 -0800 Subject: [PATCH 3/3] updated all the elements to use the new API --- packages/heml-elements/src/Block.js | 26 +++++++++----------- packages/heml-elements/src/Body.js | 18 ++++++-------- packages/heml-elements/src/Button.js | 30 +++++++++--------------- packages/heml-elements/src/Column.js | 13 ++++------ packages/heml-elements/src/Container.js | 27 ++++++++++----------- packages/heml-elements/src/Hr.js | 27 ++++++++++----------- packages/heml-elements/src/Img.js | 21 +++++++++++------ packages/heml-elements/src/Row.js | 17 +++++++------- packages/heml-elements/src/Table.js | 27 ++++++++++----------- packages/heml-elements/src/Typography.js | 15 ++++++------ 10 files changed, 99 insertions(+), 122 deletions(-) diff --git a/packages/heml-elements/src/Block.js b/packages/heml-elements/src/Block.js index 848a5d1..a8668a8 100644 --- a/packages/heml-elements/src/Block.js +++ b/packages/heml-elements/src/Block.js @@ -19,15 +19,11 @@ export default createElement('block', { containsText: true, rules: { - '.block': [ { '@pseudo': 'root' }, { display: trueHide('block') }, margin, width ], - - '.block__table__ie': [ 'width', 'max-width', { [margin]: ieAlignFallback } ], - - '.block__table': [ { '@pseudo': 'table' }, table ], - - '.block__row': [ { '@pseudo': 'row' } ], - - '.block__cell': [ { '@pseudo': 'cell' }, height, background, box, padding, border, borderRadius, 'vertical-align' ] + root: [ { display: trueHide('block') }, margin, width ], + table__ie: [ 'width', 'max-width', { [margin]: ieAlignFallback } ], + table: [ table ], + row: [ ], + cell: [ height, background, box, padding, border, borderRadius, 'vertical-align' ] }, css (Style) { @@ -35,13 +31,13 @@ export default createElement('block', { }, render (attrs, contents) { - attrs.class += ' block' + const { rules, ...defaultAttrs } = attrs return ( -
- {condition('mso | IE', `