made with esnextbin
Last active
March 25, 2021 03:57
-
-
Save cowboy/89e90aa5aad9bdac039aed1030f86c6e to your computer and use it in GitHub Desktop.
esnextbin sketch
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
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>ESNextbin Sketch</title> | |
<style> | |
#parent { border: 1px solid #77f; padding: 1em; margin: 1em; } | |
#child {} | |
.box { display: inline-block; padding: 10px; margin-right: 10px; border: 1px solid #0a0; } | |
.box:last-child { margin-right: 0; } | |
.box:empty { margin-bottom: -3px; background: #afa; } | |
.uppercase { text-transform: uppercase; } | |
</style> | |
</head> | |
<body> | |
<div id=root></div> | |
</body> | |
</html> |
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
// Note to self, see: | |
// https://github.com/Jador/react-hyperscript-helpers | |
import React from 'react'; | |
import ReactDOM from 'react-dom'; | |
/** | |
* selectorOrComponent - String or Component class | |
* props - optional object with properties | |
* children - String, React Element, or Array of any number of Strings or React Elements | |
*/ | |
function h(selectorOrComponent, ...children) { | |
// Get props object if specified. | |
let props = !isChild(children[0]) ? children.shift() : {}; | |
// Children can be passed as an array or individual arguments. Handle the array case. | |
if (Array.isArray(children[0])) { | |
[children] = children; | |
} | |
// If selector string, parse id and classNames from it. | |
if (typeof selectorOrComponent === 'string') { | |
[selectorOrComponent, props] = parseTagAndPropsFromSelector(selectorOrComponent, props); | |
} | |
// Seems smart, I saw it in https://github.com/mlmorg/react-hyperscript/blob/master/index.js | |
props = promoteNamespacedProps(props, 'dataset', 'data'); | |
// Convert camelCased ariaFoo attributes into aria-foo. | |
props = adjustPrefixedProps(props, 'aria'); | |
// Return actual React Element. | |
return React.createElement(selectorOrComponent, props, ...children); | |
} | |
// Is the given argument a valid react child? | |
const isChild = c => { | |
return typeof c === 'string' || typeof c === 'number' || Array.isArray(c) || React.isValidElement(c); | |
}; | |
// Should parse selector string: | |
// a#b => tag = 'a', props = {id: 'b'} | |
// a.c => tag = 'a', props = {className: 'c'} | |
// a.c.d => tag = 'a', props = {className: 'c d'} | |
// a#b.c.d => tag = 'a', props = {id: 'b', className: 'c d'} | |
// a.c.d#b => tag = 'a', props = {id: 'b', className: 'c d'} | |
// #b => tag = 'div', props = {id: 'b'} | |
// .c.d => tag = 'div', props = {className: 'c d'} | |
const parseTagAndPropsFromSelector = (sel, props = {}) => { | |
props = Object.assign({}, props); | |
// console.log(`SEL <${sel}>`) | |
let tag = 'div'; | |
let classNames = []; | |
let id = null; | |
const re = /([.#])?([^.#]+)/g | |
let match; | |
while (match = re.exec(sel)) { | |
const [, mode, value] = match; | |
if (mode === '.') { | |
classNames.push(value); | |
} | |
else if (mode === '#') { | |
id = value; | |
} | |
else { | |
tag = value; | |
} | |
} | |
if (classNames.length > 0) { | |
props.className = classNames.join(' '); | |
} | |
if (id) { | |
props.id = id; | |
} | |
return [tag, props]; | |
}; | |
// Should hyphenize a camelCased name. | |
// foo -> foo | |
// fooBar -> foo-bar | |
// fooBarBaz -> foo-bar-baz | |
const hyphenize = s => s.replace(/([a-z])([A-Z])/g, ([a, b]) => `${a}-${b.toLowerCase()}`); | |
// Should promote namespaced props, delete the namespace obj, return new obj. | |
// (dataset, data) {dataset, x} -> {dataset, x} | |
// (dataset, data) {dataset:{foo, bar}, x} -> {data-foo, data-bar, x} | |
// (dataset, data) {dataset:{fooBar}, x} -> {data-foo-bar, x} | |
// (dataset, data) {dataset:{fooBarBaz}, x} -> {data-foo-bar-baz, x} | |
const promoteNamespacedProps = (props, namespace, prefix) => { | |
const obj = props[namespace]; | |
if (typeof obj !== 'object') { | |
return props; | |
} | |
const result = Object.assign({}, props); | |
delete result[namespace]; | |
Object.keys(obj).forEach(key => { | |
result[`${prefix}-${hyphenize(key)}`] = obj[key]; | |
}); | |
return result; | |
}; | |
// Should adjust prefixed props and return a new object. | |
// (aria) ariaLabel -> aria-label | |
// (aria) htmlFor -> htmlFor | |
const adjustPrefixedProps = (props, prefix) => { | |
const re = new RegExp(`^${prefix}([A-Z])`); | |
return Object.keys(props).reduce((memo, key) => { | |
memo[re.test(key) ? hyphenize(key) : key] = props[key]; | |
return memo; | |
}, {}); | |
}; | |
// From https://github.com/Jador/react-hyperscript-helpers/blob/master/src/tag-names.js | |
const TAG_NAMES = [ | |
'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', | |
'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', | |
'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', | |
'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', | |
'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', | |
'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'menuitem', | |
'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', | |
'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', | |
'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', | |
'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'video', | |
'wbr', 'circle', 'clipPath', 'defs', 'ellipse', 'g', 'image', 'line', 'linearGradient', 'mask', | |
'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'text', 'tspan' | |
]; | |
// A selector is a string comprised of only .aaa (class) and #bbb (id) parts. | |
const isSelector = s => typeof s === 'string' && /^([.#][^.#\s]+)+$/.test(s); | |
// getTagWrapperFn(h, 'h1')('#foo') should call h('h1#foo') | |
// getTagWrapperFn(h, 'h1')('.bar') should call h('h1.bar') | |
// getTagWrapperFn(h, 'h1')('baz!') should call h('h1', 'baz!') | |
const getTagWrapperFn = (fn, tag) => (sel, ...args) => { | |
// todo: better validate that sel is a class-and-id-selector-string | |
if (isSelector(sel)) { | |
return fn(`${tag}${sel}`, ...args); | |
} | |
return fn(tag, sel, ...args); | |
}; | |
// todo: set props on module.exports so you can do either of the following: | |
// import {div, h1, h2, h3, p, a} from 'this-library'; | |
// import * as $ from 'this-library'; | |
TAG_NAMES.forEach(tag => { | |
window[tag] = getTagWrapperFn(h, tag); | |
}); | |
// Actual app code would look like this: | |
ReactDOM.render(( | |
div('#parent', [ | |
h1(a({href: 'http://bocoup.com/'}, span('h1 header'))), | |
h2('.uppercase', 'h2 sub header'), | |
h3('#child.uppercase', {ariaLabel: "hello", style: {color: 'red'}}, 'h3 sub sub header'), | |
span('.box'), | |
span({className: 'box'}), | |
span('.box', span('.box', span('.box', span('.box')))), | |
span('.box', span('.box'), span('.box'), span('.box')), | |
p([ | |
'p before anchor ', | |
a({href: 'http://bocoup.com/', className: 'uppercase'}, 'Bocoup!'), | |
' after anchor', | |
]), | |
]) | |
), document.getElementById('root')); |
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
{ | |
"name": "esnextbin-sketch", | |
"version": "0.0.0", | |
"dependencies": { | |
"react": "15.3.1", | |
"react-dom": "15.3.1", | |
"babel-runtime": "6.11.6" | |
} | |
} |
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
'use strict'; | |
var _keys = require('babel-runtime/core-js/object/keys'); | |
var _keys2 = _interopRequireDefault(_keys); | |
var _typeof2 = require('babel-runtime/helpers/typeof'); | |
var _typeof3 = _interopRequireDefault(_typeof2); | |
var _extends2 = require('babel-runtime/helpers/extends'); | |
var _extends3 = _interopRequireDefault(_extends2); | |
var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); | |
var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); | |
var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); | |
var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); | |
var _react = require('react'); | |
var _react2 = _interopRequireDefault(_react); | |
var _reactDom = require('react-dom'); | |
var _reactDom2 = _interopRequireDefault(_reactDom); | |
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |
/** | |
* selectorOrComponent - String or Component class | |
* props - optional object with properties | |
* children - String, React Element, or Array of any number of Strings or React Elements | |
*/ | |
// Note to self, see: | |
// https://github.com/Jador/react-hyperscript-helpers | |
function h(selectorOrComponent) { | |
for (var _len = arguments.length, children = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | |
children[_key - 1] = arguments[_key]; | |
} | |
// Get props object if specified. | |
var props = !isChild(children[0]) ? children.shift() : {}; | |
// Children can be passed as an array or individual arguments. Handle the array case. | |
if (Array.isArray(children[0])) { | |
var _children = children; | |
var _children2 = (0, _slicedToArray3.default)(_children, 1); | |
children = _children2[0]; | |
} | |
// If selector string, parse id and classNames from it. | |
if (typeof selectorOrComponent === 'string') { | |
var _parseTagAndPropsFrom = parseTagAndPropsFromSelector(selectorOrComponent, props); | |
var _parseTagAndPropsFrom2 = (0, _slicedToArray3.default)(_parseTagAndPropsFrom, 2); | |
selectorOrComponent = _parseTagAndPropsFrom2[0]; | |
props = _parseTagAndPropsFrom2[1]; | |
} | |
// Seems smart, I saw it in https://github.com/mlmorg/react-hyperscript/blob/master/index.js | |
props = promoteNamespacedProps(props, 'dataset', 'data'); | |
// Convert camelCased ariaFoo attributes into aria-foo. | |
props = adjustPrefixedProps(props, 'aria'); | |
// Return actual React Element. | |
return _react2.default.createElement.apply(_react2.default, [selectorOrComponent, props].concat((0, _toConsumableArray3.default)(children))); | |
} | |
// Is the given argument a valid react child? | |
var isChild = function isChild(c) { | |
return typeof c === 'string' || typeof c === 'number' || Array.isArray(c) || _react2.default.isValidElement(c); | |
}; | |
// Should parse selector string: | |
// a#b => tag = 'a', props = {id: 'b'} | |
// a.c => tag = 'a', props = {className: 'c'} | |
// a.c.d => tag = 'a', props = {className: 'c d'} | |
// a#b.c.d => tag = 'a', props = {id: 'b', className: 'c d'} | |
// a.c.d#b => tag = 'a', props = {id: 'b', className: 'c d'} | |
// #b => tag = 'div', props = {id: 'b'} | |
// .c.d => tag = 'div', props = {className: 'c d'} | |
var parseTagAndPropsFromSelector = function parseTagAndPropsFromSelector(sel) { | |
var props = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | |
props = (0, _extends3.default)({}, props); | |
// console.log(`SEL <${sel}>`) | |
var tag = 'div'; | |
var classNames = []; | |
var id = null; | |
var re = /([.#])?([^.#]+)/g; | |
var match = void 0; | |
while (match = re.exec(sel)) { | |
var _match = match; | |
var _match2 = (0, _slicedToArray3.default)(_match, 3); | |
var mode = _match2[1]; | |
var value = _match2[2]; | |
if (mode === '.') { | |
classNames.push(value); | |
} else if (mode === '#') { | |
id = value; | |
} else { | |
tag = value; | |
} | |
} | |
if (classNames.length > 0) { | |
props.className = classNames.join(' '); | |
} | |
if (id) { | |
props.id = id; | |
} | |
return [tag, props]; | |
}; | |
// Should hyphenize a camelCased name. | |
// foo -> foo | |
// fooBar -> foo-bar | |
// fooBarBaz -> foo-bar-baz | |
var hyphenize = function hyphenize(s) { | |
return s.replace(/([a-z])([A-Z])/g, function (_ref) { | |
var _ref2 = (0, _slicedToArray3.default)(_ref, 2); | |
var a = _ref2[0]; | |
var b = _ref2[1]; | |
return a + '-' + b.toLowerCase(); | |
}); | |
}; | |
// Should promote namespaced props, delete the namespace obj, return new obj. | |
// (dataset, data) {dataset, x} -> {dataset, x} | |
// (dataset, data) {dataset:{foo, bar}, x} -> {data-foo, data-bar, x} | |
// (dataset, data) {dataset:{fooBar}, x} -> {data-foo-bar, x} | |
// (dataset, data) {dataset:{fooBarBaz}, x} -> {data-foo-bar-baz, x} | |
var promoteNamespacedProps = function promoteNamespacedProps(props, namespace, prefix) { | |
var obj = props[namespace]; | |
if ((typeof obj === 'undefined' ? 'undefined' : (0, _typeof3.default)(obj)) !== 'object') { | |
return props; | |
} | |
var result = (0, _extends3.default)({}, props); | |
delete result[namespace]; | |
(0, _keys2.default)(obj).forEach(function (key) { | |
result[prefix + '-' + hyphenize(key)] = obj[key]; | |
}); | |
return result; | |
}; | |
// Should adjust prefixed props and return a new object. | |
// (aria) ariaLabel -> aria-label | |
// (aria) htmlFor -> htmlFor | |
var adjustPrefixedProps = function adjustPrefixedProps(props, prefix) { | |
var re = new RegExp('^' + prefix + '([A-Z])'); | |
return (0, _keys2.default)(props).reduce(function (memo, key) { | |
memo[re.test(key) ? hyphenize(key) : key] = props[key]; | |
return memo; | |
}, {}); | |
}; | |
// From https://github.com/Jador/react-hyperscript-helpers/blob/master/src/tag-names.js | |
var TAG_NAMES = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'video', 'wbr', 'circle', 'clipPath', 'defs', 'ellipse', 'g', 'image', 'line', 'linearGradient', 'mask', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'text', 'tspan']; | |
// A selector is a string comprised of only .aaa (class) and #bbb (id) parts. | |
var isSelector = function isSelector(s) { | |
return typeof s === 'string' && /^([.#][^.#\s]+)+$/.test(s); | |
}; | |
// getTagWrapperFn(h, 'h1')('#foo') should call h('h1#foo') | |
// getTagWrapperFn(h, 'h1')('.bar') should call h('h1.bar') | |
// getTagWrapperFn(h, 'h1')('baz!') should call h('h1', 'baz!') | |
var getTagWrapperFn = function getTagWrapperFn(fn, tag) { | |
return function (sel) { | |
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { | |
args[_key2 - 1] = arguments[_key2]; | |
} | |
// todo: better validate that sel is a class-and-id-selector-string | |
if (isSelector(sel)) { | |
return fn.apply(undefined, ['' + tag + sel].concat(args)); | |
} | |
return fn.apply(undefined, [tag, sel].concat(args)); | |
}; | |
}; | |
// todo: set props on module.exports so you can do either of the following: | |
// import {div, h1, h2, h3, p, a} from 'this-library'; | |
// import * as $ from 'this-library'; | |
TAG_NAMES.forEach(function (tag) { | |
window[tag] = getTagWrapperFn(h, tag); | |
}); | |
// Actual app code would look like this: | |
_reactDom2.default.render(div('#parent', [h1(a({ href: 'http://bocoup.com/' }, span('h1 header'))), h2('.uppercase', 'h2 sub header'), h3('#child.uppercase', { ariaLabel: "hello", style: { color: 'red' } }, 'h3 sub sub header'), span('.box'), span({ className: 'box' }), span('.box', span('.box', span('.box', span('.box')))), span('.box', span('.box'), span('.box'), span('.box')), p(['p before anchor ', a({ href: 'http://bocoup.com/', className: 'uppercase' }, 'Bocoup!'), ' after anchor'])]), document.getElementById('root')); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment